diff --git a/frontend/index.html b/frontend/index.html index 1242bfb..521bbd1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,7 @@ - - - + Gander diff --git a/frontend/src/components/Layout/ListItem.tsx b/frontend/src/components/Layout/ListItem.tsx index f8d9157..380f601 100644 --- a/frontend/src/components/Layout/ListItem.tsx +++ b/frontend/src/components/Layout/ListItem.tsx @@ -39,7 +39,7 @@ const StreamListItem: React.FC = ({

{title}

{username}

-

{streamCategory}

+

{!window.location.href.includes('/category/') ? streamCategory : ""}

{viewers} viewers

@@ -149,13 +149,6 @@ const VodListItem: React.FC = ({ {variant === "vodDashboard" && (
- {/* */} = ({ variant = "default" }) => { - const { isLoggedIn } = useAuth(); + const { isLoggedIn, isLive } = useAuth(); const { showAuthModal, setShowAuthModal } = useAuthModal(); const { showSideBar } = useSidebar(); const { showQuickSettings, setShowQuickSettings } = useQuickSettings(); 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 = () => { @@ -57,7 +53,10 @@ const Navbar: React.FC = ({ variant = "default" }) => { className={`relative flex justify-evenly items-center ${variant === "home" ? "h-[45vh] flex-col" : "h-[15vh] col-span-2 flex-row"}`} > {isLoggedIn && window.innerWidth > 900 && } - + {/* Login / Logout Button */} @@ -372,8 +369,8 @@ const StreamDashboard: React.FC = ({ 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"); diff --git a/frontend/src/hooks/useContent.ts b/frontend/src/hooks/useContent.ts index 16ddb65..88e6ba1 100644 --- a/frontend/src/hooks/useContent.ts +++ b/frontend/src/hooks/useContent.ts @@ -105,7 +105,6 @@ export function useStreams(customUrl?: string): { const [streams, setStreams] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(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([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(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); + // 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) { + 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); } - } - } finally { - setIsLoading(false); - } - }; - - fetchCategories(); + }; + + fetchCategories(); }, [isLoggedIn, customUrl]); - + return { categories, isLoading, error }; - } +} export function useVods(customUrl?: string): { vods: VodType[]; diff --git a/frontend/src/pages/AllCategoriesPage.tsx b/frontend/src/pages/AllCategoriesPage.tsx index 8bad3c3..8b37c4b 100644 --- a/frontend/src/pages/AllCategoriesPage.tsx +++ b/frontend/src/pages/AllCategoriesPage.tsx @@ -8,96 +8,89 @@ import { CategoryType } from "../types/CategoryType"; import { getCategoryThumbnail } from "../utils/thumbnailUtils"; const AllCategoriesPage: React.FC = () => { - const [categories, setCategories] = useState([]); - const navigate = useNavigate(); - const [categoryOffset, setCategoryOffset] = useState(0); - const [noCategories, setNoCategories] = useState(12); - const [hasMoreData, setHasMoreData] = useState(true); - - const listRowRef = useRef(null); - const isLoading = useRef(false); - - const fetchCategories = async () => { - // If already loading, skip this fetch - if (isLoading.current) return; - - 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(); - - if (data.length === 0) { - setHasMoreData(false); - return []; - } + const [categories, setCategories] = useState([]); + const navigate = useNavigate(); + const [categoryOffset, setCategoryOffset] = useState(0); + const [noCategories, setNoCategories] = useState(12); + const [hasMoreData, setHasMoreData] = useState(true); - setCategoryOffset(prev => prev + data.length); + const listRowRef = useRef(null); + const isLoading = useRef(false); - 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 fetchCategories = async () => { + // If already loading, skip this fetch + if (isLoading.current) return; - setCategories(prev => [...prev, ...processedCategories]); - return processedCategories; - } catch (error) { - console.error("Error fetching categories:", error); - return []; - } finally { - isLoading.current = false; - } - }; + isLoading.current = true; - useEffect(() => { - fetchCategories(); - }, []); + 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(); - const loadOnScroll = async () => { - if (hasMoreData && listRowRef.current) { - const newCategories = await fetchCategories(); - if (newCategories?.length > 0) { - listRowRef.current.addMoreItems(newCategories); - } - } - }; + if (data.length === 0) { + setHasMoreData(false); + return []; + } - fetchContentOnScroll(loadOnScroll, hasMoreData); + setCategoryOffset((prev) => prev + data.length); - if (hasMoreData && !categories.length) return ; + 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 handleCategoryClick = (categoryName: string) => { - console.log(categoryName); - navigate(`/category/${categoryName}`); - }; + setCategories((prev) => [...prev, ...processedCategories]); + return processedCategories; + } catch (error) { + console.error("Error fetching categories:", error); + return []; + } finally { + isLoading.current = false; + } + }; - return ( - - - {!hasMoreData && !categories.length && ( -
- No more categories to load -
- )} -
- ); + useEffect(() => { + fetchCategories(); + }, []); + + const loadOnScroll = async () => { + if (hasMoreData && listRowRef.current) { + const newCategories = await fetchCategories(); + if (newCategories?.length > 0) { + listRowRef.current.addMoreItems(newCategories); + } + } + }; + + fetchContentOnScroll(loadOnScroll, hasMoreData); + + if (hasMoreData && !categories.length) return ; + + const handleCategoryClick = (categoryName: string) => { + navigate(`/category/${categoryName}`); + }; + + return ( + + + {!hasMoreData && !categories.length &&
No more categories to load
} +
+ ); }; -export default AllCategoriesPage; \ No newline at end of file +export default AllCategoriesPage; diff --git a/frontend/src/pages/CategoryPage.tsx b/frontend/src/pages/CategoryPage.tsx index a02bb7b..8019554 100644 --- a/frontend/src/pages/CategoryPage.tsx +++ b/frontend/src/pages/CategoryPage.tsx @@ -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([]); - const listRowRef = useRef(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([]); + const listRowRef = useRef(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 ; + if (hasMoreData && !streams.length) return ; - return ( - -
- - {isLoggedIn && ( - - )} - -
+ return ( + +
+ + {isLoggedIn && ( + + )} + +
- {streams.length === 0 && !isLoading && ( -
- No live streams found in this category -
- )} -
- ); + {streams.length === 0 && !isLoading && ( +
No live streams found in this category
+ )} +
+ ); }; export default CategoryPage;