From c71ffaf8d72d4ed598fc4c84526a1bd2c1666887 Mon Sep 17 00:00:00 2001 From: Chris-1010 <122332721@umail.ucc.ie> Date: Wed, 5 Mar 2025 13:52:24 +0000 Subject: [PATCH 1/2] REFACTOR: Clean code, don't show category name on `categoryPage`, fix path, remove console logs --- frontend/index.html | 4 +- frontend/src/components/Layout/ListItem.tsx | 9 +- frontend/src/components/Navigation/Navbar.tsx | 6 +- .../src/components/Stream/StreamDashboard.tsx | 69 +++--- frontend/src/hooks/useContent.ts | 158 +++++++------- frontend/src/pages/AllCategoriesPage.tsx | 157 +++++++------- frontend/src/pages/CategoryPage.tsx | 197 +++++++++--------- 7 files changed, 281 insertions(+), 319 deletions(-) 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 [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 = () => { diff --git a/frontend/src/components/Stream/StreamDashboard.tsx b/frontend/src/components/Stream/StreamDashboard.tsx index c33ebac..9153da5 100644 --- a/frontend/src/components/Stream/StreamDashboard.tsx +++ b/frontend/src/components/Stream/StreamDashboard.tsx @@ -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 { + streamKey: string; } interface StreamDashboardProps { @@ -26,10 +23,10 @@ interface StreamDashboardProps { const StreamDashboard: React.FC = ({ username, userId, isLive }) => { const [streamData, setStreamData] = useState({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ username, userId, isL
setIsCategoryFocused(true)} onBlur={() => setTimeout(() => setIsCategoryFocused(false), 200)} @@ -321,16 +318,16 @@ const StreamDashboard: React.FC = ({ username, userId, isL {isLive && (

Stream Info

-

Viewers: {streamData.viewer_count}

+

Viewers: {streamData.viewers}

- Started: {new Date(streamData.start_time!).toLocaleTimeString()} + Started: {new Date(streamData.startTime!).toLocaleTimeString()} {` (${timeStarted})`}

)}
- + @@ -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; From 2ec1983e92a02be802b1892e2266e4be117c65d0 Mon Sep 17 00:00:00 2001 From: Chris-1010 <122332721@umail.ucc.ie> Date: Wed, 5 Mar 2025 14:10:56 +0000 Subject: [PATCH 2/2] REFACTOR: Change Stream Dashboard button depending on live status --- frontend/src/components/Navigation/Navbar.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Navigation/Navbar.tsx b/frontend/src/components/Navigation/Navbar.tsx index d5ebc00..96fa801 100644 --- a/frontend/src/components/Navigation/Navbar.tsx +++ b/frontend/src/components/Navigation/Navbar.tsx @@ -16,7 +16,7 @@ interface NavbarProps { } const Navbar: React.FC = ({ variant = "default" }) => { - const { isLoggedIn } = useAuth(); + const { isLoggedIn, isLive } = useAuth(); const { showAuthModal, setShowAuthModal } = useAuthModal(); const { showSideBar } = useSidebar(); const { showQuickSettings, setShowQuickSettings } = useQuickSettings(); @@ -53,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 */}