REFACTOR: Clean code, don't show category name on categoryPage, fix path, remove console logs

This commit is contained in:
Chris-1010
2025-03-05 13:52:24 +00:00
parent 37156ead22
commit c71ffaf8d7
7 changed files with 281 additions and 319 deletions

View File

@@ -2,9 +2,7 @@
<html lang="en" class="min-w-[850px]">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<!-- <link rel="icon" href="public/images/favicon_monke.ico" /> -->
<link rel="icon" href="public/images/favicon_goose.ico" />
<link rel="icon" href="/images/favicon_goose.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gander</title>
</head>

View File

@@ -39,7 +39,7 @@ const StreamListItem: React.FC<StreamListItemProps> = ({
<div className="p-3">
<h3 className="font-semibold text-lg text-center truncate max-w-full">{title}</h3>
<p className="font-bold">{username}</p>
<p className="text-sm text-gray-300">{streamCategory}</p>
<p className="text-sm text-gray-300">{!window.location.href.includes('/category/') ? streamCategory : ""}</p>
<p className="text-sm text-gray-300">{viewers} viewers</p>
</div>
</div>
@@ -149,13 +149,6 @@ const VodListItem: React.FC<VodListItemProps> = ({
</div>
{variant === "vodDashboard" && (
<div className="flex justify-evenly items-stretch rounded-b-lg">
{/* <button
className="flex justify-around w-full h-full bg-black/50 hover:bg-black/80 p-2 mx-1 font-semibold rounded-full border border-transparent hover:border-white"
onClick={() => console.log("Publish")}
>
<UploadIcon />
Publish
</button> */}
<a
className="flex justify-around w-full h-full bg-black/50 hover:bg-black/80 p-2 mx-1 font-semibold rounded-full border border-transparent hover:border-white"
href={`/vods/${username}/${vod_id}.mp4`}

View File

@@ -23,13 +23,9 @@ const Navbar: React.FC<NavbarProps> = ({ variant = "default" }) => {
const [justToggled, setJustToggled] = React.useState(false);
const handleLogout = () => {
console.log("Logging out...");
fetch("/api/logout")
.then((response) => response.json())
.then((data) => {
console.log(data);
window.location.reload();
});
.then(() => window.location.reload());
};
const handleQuickSettings = () => {

View File

@@ -8,13 +8,10 @@ import VideoPlayer from "../../components/Stream/VideoPlayer";
import { CategoryType } from "../../types/CategoryType";
import { StreamListItem } from "../../components/Layout/ListItem";
import { getCategoryThumbnail } from "../../utils/thumbnailUtils";
import { StreamType } from "../../types/StreamType";
interface StreamData {
title: string;
category_name: string;
viewer_count: number;
start_time: string;
stream_key: string;
interface StreamData extends Omit<StreamType, "type" | "username" | "id"> {
streamKey: string;
}
interface StreamDashboardProps {
@@ -26,10 +23,10 @@ interface StreamDashboardProps {
const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isLive }) => {
const [streamData, setStreamData] = useState<StreamData>({
title: "",
category_name: "",
viewer_count: 0,
start_time: "",
stream_key: "",
streamCategory: "",
viewers: 0,
startTime: "",
streamKey: "",
});
const [timeStarted, setTimeStarted] = useState("");
const [streamDetected, setStreamDetected] = useState(false);
@@ -79,17 +76,17 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
try {
if (isLive) {
const streamResponse = await fetch(`/api/streams/${userId}/data`, { credentials: "include" });
const streamData = await streamResponse.json();
const data = await streamResponse.json();
setStreamData({
title: streamData.title,
category_name: streamData.category_name,
viewer_count: streamData.num_viewers,
start_time: streamData.start_time,
stream_key: streamData.stream_key,
title: data.title,
streamCategory: data.category_name,
viewers: data.num_viewers,
startTime: data.start_time,
streamKey: data.stream_key,
});
const time = Math.floor(
(Date.now() - new Date(streamData.start_time).getTime()) / 60000 // Convert to minutes
(Date.now() - new Date(data.startTime).getTime()) / 60000 // Convert to minutes
);
if (time < 60) setTimeStarted(`${time}m ago`);
@@ -101,7 +98,7 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
const keyData = await response.json();
setStreamData((prev) => ({
...prev,
stream_key: keyData.stream_key,
streamKey: keyData.stream_key,
}));
}
} catch (error) {
@@ -113,7 +110,7 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
const { name, value } = e.target;
setStreamData((prev) => ({ ...prev, [name]: value }));
if (name === "category_name") {
if (name === "streamCategory") {
const filtered = categories.filter((cat: CategoryType) => cat.title.toLowerCase().includes(value.toLowerCase()));
setFilteredCategories(filtered);
if (debouncedCheck) {
@@ -124,7 +121,7 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
const handleCategorySelect = (categoryName: string) => {
console.log("Selected category:", categoryName);
setStreamData((prev) => ({ ...prev, category_name: categoryName }));
setStreamData((prev) => ({ ...prev, streamCategory: categoryName }));
setFilteredCategories([]);
if (debouncedCheck) {
debouncedCheck(categoryName);
@@ -141,8 +138,8 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
});
} else {
setThumbnail(null);
if (streamData.category_name && debouncedCheck) {
debouncedCheck(streamData.category_name);
if (streamData.streamCategory && debouncedCheck) {
debouncedCheck(streamData.streamCategory);
} else {
setThumbnailPreview({ url: "", isCustom: false });
}
@@ -151,9 +148,9 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
const clearThumbnail = () => {
setThumbnail(null);
if (streamData.category_name) {
if (streamData.streamCategory) {
console.log("Clearing thumbnail as category is set and default category thumbnail will be used");
const defaultThumbnail = getCategoryThumbnail(streamData.category_name);
const defaultThumbnail = getCategoryThumbnail(streamData.streamCategory);
setThumbnailPreview({ url: defaultThumbnail, isCustom: false });
} else {
setThumbnailPreview({ url: "", isCustom: false });
@@ -163,8 +160,8 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
const isFormValid = () => {
return (
streamData.title.trim() !== "" &&
streamData.category_name.trim() !== "" &&
categories.some((cat: CategoryType) => cat.title.toLowerCase() === streamData.category_name.toLowerCase()) &&
streamData.streamCategory.trim() !== "" &&
categories.some((cat: CategoryType) => cat.title.toLowerCase() === streamData.streamCategory.toLowerCase()) &&
streamDetected
);
};
@@ -198,9 +195,9 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
console.log("Updating stream with data:", streamData);
const formData = new FormData();
formData.append("key", streamData.stream_key);
formData.append("key", streamData.streamKey);
formData.append("title", streamData.title);
formData.append("category_name", streamData.category_name);
formData.append("streamCategory", streamData.streamCategory);
if (thumbnail) {
formData.append("thumbnail", thumbnail);
}
@@ -231,7 +228,7 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ key: streamData.stream_key }),
body: JSON.stringify({ key: streamData.streamKey }),
});
if (response.ok) {
@@ -267,8 +264,8 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
<div className="relative">
<label className="block text-white mb-2">Category</label>
<Input
name="category_name"
value={streamData.category_name}
name="streamCategory"
value={streamData.streamCategory}
onChange={handleInputChange}
onFocus={() => setIsCategoryFocused(true)}
onBlur={() => setTimeout(() => setIsCategoryFocused(false), 200)}
@@ -321,16 +318,16 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
{isLive && (
<div className="bg-gray-700 p-4 rounded-lg">
<h3 className="text-white font-semibold mb-2">Stream Info</h3>
<p className="text-gray-300">Viewers: {streamData.viewer_count}</p>
<p className="text-gray-300">Viewers: {streamData.viewers}</p>
<p className="text-gray-300">
Started: {new Date(streamData.start_time!).toLocaleTimeString()}
Started: {new Date(streamData.startTime!).toLocaleTimeString()}
{` (${timeStarted})`}
</p>
</div>
)}
<div className="flex items-center mx-auto p-10 bg-gray-900 w-fit rounded-xl py-4">
<label className="block text-white mr-8">Stream Key</label>
<Input type={showKey ? "text" : "password"} value={streamData.stream_key} readOnly extraClasses="w-fit pr-[30px]" disabled />
<Input type={showKey ? "text" : "password"} value={streamData.streamKey} readOnly extraClasses="w-fit pr-[30px]" disabled />
<button type="button" onClick={() => setShowKey(!showKey)} className="-translate-x-[30px] top-1/2 h-6 w-6 text-white">
{showKey ? <HideIcon className="h-6 w-6" /> : <ShowIcon className="h-6 w-6" />}
</button>
@@ -372,8 +369,8 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
id={1}
title={streamData.title || "Stream Title"}
username={username || ""}
streamCategory={streamData.category_name || "Category"}
viewers={streamData.viewer_count}
streamCategory={streamData.streamCategory || "Category"}
viewers={streamData.viewers}
thumbnail={thumbnailPreview.url || ""}
onItemClick={() => {
window.open(`/${username}`, "_blank");

View File

@@ -105,7 +105,6 @@ export function useStreams(customUrl?: string): {
const [streams, setStreams] = useState<StreamType[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
console.log(streams)
useEffect(() => {
const fetchStreams = async () => {
@@ -159,7 +158,6 @@ export function useStreams(customUrl?: string): {
}
const data = await response.json();
console.log("Data: ", data)
// Make sure it is 100% ARRAY NOT OBJECT
const formattedData = Array.isArray(data) ? data : [data];
@@ -196,94 +194,92 @@ export function useCategories(customUrl?: string): {
categories: CategoryType[];
isLoading: boolean;
error: string | null;
} {
} {
const { isLoggedIn } = useAuth();
const [categories, setCategories] = useState<CategoryType[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchCategories = async () => {
setIsLoading(true);
try {
// Always fetch the recommended categories if logged in
if (isLoggedIn && !customUrl) {
const recommendedResponse = await fetch("/api/categories/recommended");
if (!recommendedResponse.ok) {
throw new Error(`Error fetching recommended categories: ${recommendedResponse.status}`);
}
const recommendedData = await recommendedResponse.json();
const processedRecommended = processCategoryData(recommendedData);
// If we have at least 4 recommended categories, use just those
if (processedRecommended.length >= 4) {
setCategories(processedRecommended);
}
// If we have fewer than 4, fetch popular categories to fill the gap
else {
const popularResponse = await fetch(`/api/categories/popular/8`);
if (!popularResponse.ok) {
throw new Error(`Error fetching popular categories: ${popularResponse.status}`);
}
const popularData = await popularResponse.json();
const processedPopular = processCategoryData(popularData);
// Get IDs of recommended categories to avoid duplicates
const recommendedIds = processedRecommended.map(cat => cat.id);
// Filter popular categories to only include ones not in recommended
const uniquePopularCategories = processedPopular.filter(
popularCat => !recommendedIds.includes(popularCat.id)
);
// Combine with recommended categories first to maintain priority
const combinedCategories = [...processedRecommended, ...uniquePopularCategories];
setCategories(combinedCategories);
}
}
// For custom URL or not logged in, use the original approach
else {
const url = customUrl || "/api/categories/popular/4";
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error fetching categories: ${response.status}`);
}
const data = await response.json();
setCategories(processCategoryData(data));
}
setError(null);
} catch (err) {
console.error("Error in useCategories:", err);
setError(err instanceof Error ? err.message : "Unknown error");
// Fallback to popular categories on error
if (!customUrl) {
const fetchCategories = async () => {
setIsLoading(true);
try {
const fallbackResponse = await fetch("/api/categories/popular/4");
if (fallbackResponse.ok) {
const fallbackData = await fallbackResponse.json();
setCategories(processCategoryData(fallbackData));
}
} catch (fallbackErr) {
console.error("Error fetching fallback categories:", fallbackErr);
}
}
} finally {
setIsLoading(false);
}
};
// Always fetch the recommended categories if logged in
if (isLoggedIn && !customUrl) {
const recommendedResponse = await fetch("/api/categories/recommended");
if (!recommendedResponse.ok) {
throw new Error(`Error fetching recommended categories: ${recommendedResponse.status}`);
}
fetchCategories();
const recommendedData = await recommendedResponse.json();
const processedRecommended = processCategoryData(recommendedData);
// If we have at least 4 recommended categories, use just those
if (processedRecommended.length >= 4) {
setCategories(processedRecommended);
}
// If we have fewer than 4, fetch popular categories to fill the gap
else {
const popularResponse = await fetch(`/api/categories/popular/8`);
if (!popularResponse.ok) {
throw new Error(`Error fetching popular categories: ${popularResponse.status}`);
}
const popularData = await popularResponse.json();
const processedPopular = processCategoryData(popularData);
// Get IDs of recommended categories to avoid duplicates
const recommendedIds = processedRecommended.map((cat) => cat.id);
// Filter popular categories to only include ones not in recommended
const uniquePopularCategories = processedPopular.filter((popularCat) => !recommendedIds.includes(popularCat.id));
// Combine with recommended categories first to maintain priority
const combinedCategories = [...processedRecommended, ...uniquePopularCategories];
setCategories(combinedCategories);
}
}
// For custom URL or not logged in, use the original approach
else {
const url = customUrl || "/api/categories/popular/4";
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error fetching categories: ${response.status}`);
}
const data = await response.json();
setCategories(processCategoryData(data));
}
setError(null);
} catch (err) {
console.error("Error in useCategories:", err);
setError(err instanceof Error ? err.message : "Unknown error");
// Fallback to popular categories on error
if (!customUrl) {
try {
const fallbackResponse = await fetch("/api/categories/popular/4");
if (fallbackResponse.ok) {
const fallbackData = await fallbackResponse.json();
setCategories(processCategoryData(fallbackData));
}
} catch (fallbackErr) {
console.error("Error fetching fallback categories:", fallbackErr);
}
}
} finally {
setIsLoading(false);
}
};
fetchCategories();
}, [isLoggedIn, customUrl]);
return { categories, isLoading, error };
}
}
export function useVods(customUrl?: string): {
vods: VodType[];

View File

@@ -8,96 +8,89 @@ import { CategoryType } from "../types/CategoryType";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
const AllCategoriesPage: React.FC = () => {
const [categories, setCategories] = useState<CategoryType[]>([]);
const navigate = useNavigate();
const [categoryOffset, setCategoryOffset] = useState(0);
const [noCategories, setNoCategories] = useState(12);
const [hasMoreData, setHasMoreData] = useState(true);
const [categories, setCategories] = useState<CategoryType[]>([]);
const navigate = useNavigate();
const [categoryOffset, setCategoryOffset] = useState(0);
const [noCategories, setNoCategories] = useState(12);
const [hasMoreData, setHasMoreData] = useState(true);
const listRowRef = useRef<any>(null);
const isLoading = useRef(false);
const listRowRef = useRef<any>(null);
const isLoading = useRef(false);
const fetchCategories = async () => {
// If already loading, skip this fetch
if (isLoading.current) return;
const fetchCategories = async () => {
// If already loading, skip this fetch
if (isLoading.current) return;
isLoading.current = true;
isLoading.current = true;
try {
const response = await fetch(`/api/categories/popular/${noCategories}/${categoryOffset}`);
if (!response.ok) {
throw new Error("Failed to fetch categories");
}
const data = await response.json();
try {
const response = await fetch(`/api/categories/popular/${noCategories}/${categoryOffset}`);
if (!response.ok) {
throw new Error("Failed to fetch categories");
}
const data = await response.json();
if (data.length === 0) {
setHasMoreData(false);
return [];
}
if (data.length === 0) {
setHasMoreData(false);
return [];
}
setCategoryOffset(prev => prev + data.length);
setCategoryOffset((prev) => prev + data.length);
const processedCategories = data.map((category: any) => ({
type: "category" as const,
id: category.category_id,
title: category.category_name,
viewers: category.num_viewers,
thumbnail: getCategoryThumbnail(category.category_name)
}));
const processedCategories = data.map((category: any) => ({
type: "category" as const,
id: category.category_id,
title: category.category_name,
viewers: category.num_viewers,
thumbnail: getCategoryThumbnail(category.category_name),
}));
setCategories(prev => [...prev, ...processedCategories]);
return processedCategories;
} catch (error) {
console.error("Error fetching categories:", error);
return [];
} finally {
isLoading.current = false;
}
};
setCategories((prev) => [...prev, ...processedCategories]);
return processedCategories;
} catch (error) {
console.error("Error fetching categories:", error);
return [];
} finally {
isLoading.current = false;
}
};
useEffect(() => {
fetchCategories();
}, []);
useEffect(() => {
fetchCategories();
}, []);
const loadOnScroll = async () => {
if (hasMoreData && listRowRef.current) {
const newCategories = await fetchCategories();
if (newCategories?.length > 0) {
listRowRef.current.addMoreItems(newCategories);
}
}
};
const loadOnScroll = async () => {
if (hasMoreData && listRowRef.current) {
const newCategories = await fetchCategories();
if (newCategories?.length > 0) {
listRowRef.current.addMoreItems(newCategories);
}
}
};
fetchContentOnScroll(loadOnScroll, hasMoreData);
fetchContentOnScroll(loadOnScroll, hasMoreData);
if (hasMoreData && !categories.length) return <LoadingScreen />;
if (hasMoreData && !categories.length) return <LoadingScreen />;
const handleCategoryClick = (categoryName: string) => {
console.log(categoryName);
navigate(`/category/${categoryName}`);
};
const handleCategoryClick = (categoryName: string) => {
navigate(`/category/${categoryName}`);
};
return (
<DynamicPageContent
className="min-h-screen"
>
<ListRow
ref={listRowRef}
type="category"
title="All Categories"
items={categories}
onItemClick={handleCategoryClick}
extraClasses="bg-[var(--recommend)] text-center"
itemExtraClasses="w-[20vw]"
wrap={true}
/>
{!hasMoreData && !categories.length && (
<div className="text-center text-gray-500 p-4">
No more categories to load
</div>
)}
</DynamicPageContent>
);
return (
<DynamicPageContent className="min-h-screen">
<ListRow
ref={listRowRef}
type="category"
title="All Categories"
items={categories}
onItemClick={handleCategoryClick}
extraClasses="bg-[var(--recommend)] text-center"
itemExtraClasses="w-[20vw]"
wrap={true}
/>
{!hasMoreData && !categories.length && <div className="text-center text-gray-500 p-4">No more categories to load</div>}
</DynamicPageContent>
);
};
export default AllCategoriesPage;

View File

@@ -11,124 +11,113 @@ import { StreamType } from "../types/StreamType";
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
const CategoryPage: React.FC = () => {
const { categoryName } = useParams<{ categoryName: string }>();
const [streams, setStreams] = useState<StreamType[]>([]);
const listRowRef = useRef<any>(null);
const isLoading = useRef(false);
const [streamOffset, setStreamOffset] = useState(0);
const [noStreams, setNoStreams] = useState(12);
const [hasMoreData, setHasMoreData] = useState(true);
const { isLoggedIn } = useAuth();
const {
isCategoryFollowing,
checkCategoryFollowStatus,
followCategory,
unfollowCategory,
} = useCategoryFollow();
const { categoryName } = useParams<{ categoryName: string }>();
const [streams, setStreams] = useState<StreamType[]>([]);
const listRowRef = useRef<any>(null);
const isLoading = useRef(false);
const [streamOffset, setStreamOffset] = useState(0);
const [noStreams, setNoStreams] = useState(12);
const [hasMoreData, setHasMoreData] = useState(true);
const { isLoggedIn } = useAuth();
const { isCategoryFollowing, checkCategoryFollowStatus, followCategory, unfollowCategory } = useCategoryFollow();
useEffect(() => {
if (categoryName) checkCategoryFollowStatus(categoryName);
}, [categoryName]);
useEffect(() => {
if (categoryName) checkCategoryFollowStatus(categoryName);
}, [categoryName]);
const fetchCategoryStreams = async () => {
// If already loading, skip this fetch
if (isLoading.current) return;
const fetchCategoryStreams = async () => {
// If already loading, skip this fetch
if (isLoading.current) return;
isLoading.current = true;
try {
const response = await fetch(
`/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}`
);
if (!response.ok) {
throw new Error("Failed to fetch category streams");
}
const data = await response.json();
isLoading.current = true;
try {
const response = await fetch(`/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}`);
if (!response.ok) {
throw new Error("Failed to fetch category streams");
}
const data = await response.json();
if (data.length === 0) {
setHasMoreData(false);
return [];
}
if (data.length === 0) {
setHasMoreData(false);
return [];
}
setStreamOffset((prev) => prev + data.length);
setStreamOffset((prev) => prev + data.length);
const processedStreams = data.map((stream: any) => ({
type: "stream",
id: stream.user_id,
title: stream.title,
username: stream.username,
streamCategory: categoryName,
viewers: stream.num_viewers,
thumbnail: getCategoryThumbnail(categoryName, stream.thumbnail),
}));
const processedStreams = data.map((stream: any) => ({
type: "stream",
id: stream.user_id,
title: stream.title,
username: stream.username,
streamCategory: categoryName,
viewers: stream.num_viewers,
thumbnail: getCategoryThumbnail(categoryName, stream.thumbnail),
}));
setStreams((prev) => [...prev, ...processedStreams]);
return processedStreams;
} catch (error) {
console.error("Error fetching category streams:", error);
} finally {
isLoading.current = false;
}
};
setStreams((prev) => [...prev, ...processedStreams]);
return processedStreams;
} catch (error) {
console.error("Error fetching category streams:", error);
} finally {
isLoading.current = false;
}
};
useEffect(() => {
fetchCategoryStreams();
}, []);
useEffect(() => {
fetchCategoryStreams();
}, []);
const loadOnScroll = async () => {
if (hasMoreData && listRowRef.current) {
const newStreams = await fetchCategoryStreams();
if (newStreams?.length > 0) {
listRowRef.current.addMoreItems(newStreams);
} else console.log("No more data to fetch");
}
};
const loadOnScroll = async () => {
if (hasMoreData && listRowRef.current) {
const newStreams = await fetchCategoryStreams();
if (newStreams?.length > 0) {
listRowRef.current.addMoreItems(newStreams);
} else console.log("No more data to fetch");
}
};
fetchContentOnScroll(loadOnScroll, hasMoreData);
fetchContentOnScroll(loadOnScroll, hasMoreData);
const handleStreamClick = (streamerName: string) => {
window.location.href = `/${streamerName}`;
};
const handleStreamClick = (streamerName: string) => {
window.location.href = `/${streamerName}`;
};
if (hasMoreData && !streams.length) return <LoadingScreen />;
if (hasMoreData && !streams.length) return <LoadingScreen />;
return (
<DynamicPageContent className="min-h-screen">
<div className="pt-8">
<ListRow
ref={listRowRef}
type="stream"
title={`${categoryName}`}
description={`Live streams in the ${categoryName} category`}
items={streams}
wrap={true}
onItemClick={handleStreamClick}
extraClasses="bg-[var(--recommend)]"
itemExtraClasses="w-[20vw]"
>
{isLoggedIn && (
<Button
extraClasses="absolute right-10"
onClick={() => {
if (categoryName) {
isCategoryFollowing
? unfollowCategory(categoryName)
: followCategory(categoryName);
}
}}
>
{isCategoryFollowing ? "Unfollow" : "Follow"}
</Button>
)}
</ListRow>
</div>
return (
<DynamicPageContent className="min-h-screen">
<div className="pt-8">
<ListRow
ref={listRowRef}
type="stream"
title={`${categoryName}`}
description={`Live streams in the ${categoryName} category`}
items={streams}
wrap={true}
onItemClick={handleStreamClick}
extraClasses="bg-[var(--recommend)]"
itemExtraClasses="w-[20vw]"
>
{isLoggedIn && (
<Button
extraClasses="absolute right-10"
onClick={() => {
if (categoryName) {
isCategoryFollowing ? unfollowCategory(categoryName) : followCategory(categoryName);
}
}}
>
{isCategoryFollowing ? "Unfollow" : "Follow"}
</Button>
)}
</ListRow>
</div>
{streams.length === 0 && !isLoading && (
<div className="text-white text-center text-2xl mt-8">
No live streams found in this category
</div>
)}
</DynamicPageContent>
);
{streams.length === 0 && !isLoading && (
<div className="text-white text-center text-2xl mt-8">No live streams found in this category</div>
)}
</DynamicPageContent>
);
};
export default CategoryPage;