From ff422f06f79e0a99c416ed5bb653bd93c65b33e1 Mon Sep 17 00:00:00 2001 From: Chris-1010 <122332721@umail.ucc.ie> Date: Thu, 27 Feb 2025 14:01:37 +0000 Subject: [PATCH] UPDATE: Extract category thumbnail handling to separate function --- frontend/src/hooks/useContent.ts | 13 +++------ frontend/src/pages/AllCategoriesPage.tsx | 5 ++-- frontend/src/pages/CategoryPage.tsx | 8 ++--- frontend/src/pages/FollowedCategories.tsx | 3 +- frontend/src/pages/ResultsPage.tsx | 6 ++-- frontend/src/pages/StreamDashboardPage.tsx | 34 +++++++++++----------- frontend/src/pages/UserPage.tsx | 10 +++---- frontend/src/utils/thumbnailUtils.ts | 24 +++++++++++++++ 8 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 frontend/src/utils/thumbnailUtils.ts diff --git a/frontend/src/hooks/useContent.ts b/frontend/src/hooks/useContent.ts index 4a5d0b4..5e99aaf 100644 --- a/frontend/src/hooks/useContent.ts +++ b/frontend/src/hooks/useContent.ts @@ -4,6 +4,7 @@ import { useAuth } from "../context/AuthContext"; import { StreamType } from "../types/StreamType"; import { CategoryType } from "../types/CategoryType"; import { UserType } from "../types/UserType"; +import { getCategoryThumbnail } from "../utils/thumbnailUtils"; // Helper function to process API data into our consistent types const processStreamData = (data: any[]): StreamType[] => { @@ -14,12 +15,8 @@ const processStreamData = (data: any[]): StreamType[] => { username: stream.username, streamCategory: stream.category_name, viewers: stream.num_viewers, - thumbnail: - stream.thumbnail || - `/images/category_thumbnails/${stream.category_name - .toLowerCase() - .replace(/ /g, "_")}.webp`, - })); + thumbnail: getCategoryThumbnail(stream.category_name, stream.thumbnail), + })) }; const processCategoryData = (data: any[]): CategoryType[] => { @@ -28,9 +25,7 @@ const processCategoryData = (data: any[]): CategoryType[] => { id: category.category_id, title: category.category_name, viewers: category.num_viewers, - thumbnail: `/images/category_thumbnails/${category.category_name - .toLowerCase() - .replace(/ /g, "_")}.webp`, + thumbnail: getCategoryThumbnail(category.category_name) })); }; diff --git a/frontend/src/pages/AllCategoriesPage.tsx b/frontend/src/pages/AllCategoriesPage.tsx index 1b082d6..26fffcb 100644 --- a/frontend/src/pages/AllCategoriesPage.tsx +++ b/frontend/src/pages/AllCategoriesPage.tsx @@ -5,6 +5,7 @@ import DynamicPageContent from "../components/Layout/DynamicPageContent"; import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll"; import LoadingScreen from "../components/Layout/LoadingScreen"; import { CategoryType } from "../types/CategoryType"; +import { getCategoryThumbnail } from "../utils/thumbnailUtils"; const AllCategoriesPage: React.FC = () => { const [categories, setCategories] = useState([]); @@ -41,9 +42,7 @@ const AllCategoriesPage: React.FC = () => { id: category.category_id, title: category.category_name, viewers: category.num_viewers, - thumbnail: `/images/category_thumbnails/${category.category_name - .toLowerCase() - .replace(/ /g, "_")}.webp`, + thumbnail: getCategoryThumbnail(category.category_name) })); setCategories(prev => [...prev, ...processedCategories]); diff --git a/frontend/src/pages/CategoryPage.tsx b/frontend/src/pages/CategoryPage.tsx index a1af124..8562c72 100644 --- a/frontend/src/pages/CategoryPage.tsx +++ b/frontend/src/pages/CategoryPage.tsx @@ -8,6 +8,7 @@ import { useAuth } from "../context/AuthContext"; import { useCategoryFollow } from "../hooks/useCategoryFollow"; import LoadingScreen from "../components/Layout/LoadingScreen"; import { StreamType } from "../types/StreamType"; +import { getCategoryThumbnail } from "../utils/thumbnailUtils"; const CategoryPage: React.FC = () => { const { categoryName } = useParams<{ categoryName: string }>(); @@ -57,12 +58,7 @@ const CategoryPage: React.FC = () => { username: stream.username, streamCategory: categoryName, viewers: stream.num_viewers, - thumbnail: - stream.thumbnail || - (categoryName && - `/images/category_thumbnails/${categoryName - .toLowerCase() - .replace(/ /g, "_")}.webp`), + thumbnail: getCategoryThumbnail(categoryName, stream.thumbnail), })); setStreams((prev) => [...prev, ...processedStreams]); diff --git a/frontend/src/pages/FollowedCategories.tsx b/frontend/src/pages/FollowedCategories.tsx index 73d4d71..5b0ed3c 100644 --- a/frontend/src/pages/FollowedCategories.tsx +++ b/frontend/src/pages/FollowedCategories.tsx @@ -8,6 +8,7 @@ import Button from "../components/Input/Button"; import { useCategoryFollow } from "../hooks/useCategoryFollow"; import { ListItemProps as StreamData } from "../components/Layout/ListItem"; import LoadingScreen from "../components/Layout/LoadingScreen"; +import { getCategoryThumbnail } from "../utils/thumbnailUtils"; interface Category { isFollowing: any; @@ -130,7 +131,7 @@ const FollowedCategories: React.FC = ({ extraClasses = "" {category.category_name} diff --git a/frontend/src/pages/ResultsPage.tsx b/frontend/src/pages/ResultsPage.tsx index 6b4d3d6..300c9b1 100644 --- a/frontend/src/pages/ResultsPage.tsx +++ b/frontend/src/pages/ResultsPage.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import Button from "../components/Input/Button"; import SearchBar from "../components/Input/SearchBar"; import ListRow from "../components/Layout/ListRow"; import DynamicPageContent from "../components/Layout/DynamicPageContent"; +import { getCategoryThumbnail } from "../utils/thumbnailUtils"; const ResultsPage: React.FC = ({ }) => { const [overflow, setOverflow] = useState(false); @@ -65,9 +65,7 @@ const ResultsPage: React.FC = ({ }) => { type: "category", title: category.category_name, viewers: 0, - thumbnail: `/images/category_thumbnails/${category.category_name - .toLowerCase() - .replace(/ /g, "_")}.webp`, + thumbnail: getCategoryThumbnail(category.category_name), }))} title="Categories" onItemClick={(category_name: string) => diff --git a/frontend/src/pages/StreamDashboardPage.tsx b/frontend/src/pages/StreamDashboardPage.tsx index 8afddf8..1f0be5f 100644 --- a/frontend/src/pages/StreamDashboardPage.tsx +++ b/frontend/src/pages/StreamDashboardPage.tsx @@ -3,12 +3,17 @@ import DynamicPageContent from "../components/Layout/DynamicPageContent"; import Button from "../components/Input/Button"; import Input from "../components/Input/Input"; import { useCategories } from "../hooks/useContent"; -import { X as CloseIcon, Eye as ShowIcon, EyeOff as HideIcon } from "lucide-react"; +import { + X as CloseIcon, + Eye as ShowIcon, + EyeOff as HideIcon, +} from "lucide-react"; import { useAuth } from "../context/AuthContext"; import { debounce } from "lodash"; import VideoPlayer from "../components/Stream/VideoPlayer"; import { CategoryType } from "../types/CategoryType"; import { StreamListItem } from "../components/Layout/ListItem"; +import { getCategoryThumbnail } from "../utils/thumbnailUtils"; interface StreamData { title: string; @@ -31,7 +36,9 @@ const StreamDashboardPage: React.FC = () => { const [streamDetected, setStreamDetected] = useState(false); const [timeStarted, setTimeStarted] = useState(""); const [isCategoryFocused, setIsCategoryFocused] = useState(false); - const [filteredCategories, setFilteredCategories] = useState([]); + const [filteredCategories, setFilteredCategories] = useState( + [] + ); const [thumbnail, setThumbnail] = useState(null); const [thumbnailPreview, setThumbnailPreview] = useState<{ url: string; @@ -40,10 +47,10 @@ const StreamDashboardPage: React.FC = () => { const [debouncedCheck, setDebouncedCheck] = useState(null); const [showKey, setShowKey] = useState(false); - const { + const { categories, - isLoading: categoriesLoading, - error: categoriesError + isLoading: categoriesLoading, + error: categoriesError, } = useCategories("/api/categories/popular/100"); useEffect(() => { @@ -60,9 +67,7 @@ const StreamDashboardPage: React.FC = () => { ); if (isValidCategory && !thumbnailPreview.isCustom) { - const defaultThumbnail = `/images/thumbnails/categories/${categoryName - .toLowerCase() - .replace(/ /g, "_")}.webp`; + const defaultThumbnail = getCategoryThumbnail(categoryName); setThumbnailPreview({ url: defaultThumbnail, isCustom: false }); } }, 300); @@ -82,7 +87,7 @@ const StreamDashboardPage: React.FC = () => { const checkStreamStatus = async () => { if (!username) return; - + try { const response = await fetch(`/api/user/${username}/status`); const data = await response.json(); @@ -179,9 +184,7 @@ const StreamDashboardPage: React.FC = () => { console.log( "Clearing thumbnail as category is set and default category thumbnail will be used" ); - const defaultThumbnail = `/images/thumbnails/categories/${streamData.category_name - .toLowerCase() - .replace(/ /g, "_")}.webp`; + const defaultThumbnail = getCategoryThumbnail(streamData.category_name); setThumbnailPreview({ url: defaultThumbnail, isCustom: false }); } else { setThumbnailPreview({ url: "", isCustom: false }); @@ -194,8 +197,7 @@ const StreamDashboardPage: React.FC = () => { streamData.category_name.trim() !== "" && categories.some( (cat) => - cat.title.toLowerCase() === - streamData.category_name.toLowerCase() + cat.title.toLowerCase() === streamData.category_name.toLowerCase() ) && streamDetected ); @@ -328,9 +330,7 @@ const StreamDashboardPage: React.FC = () => {
- handleCategorySelect(category.title) - } + onClick={() => handleCategorySelect(category.title)} > {category.title}
diff --git a/frontend/src/pages/UserPage.tsx b/frontend/src/pages/UserPage.tsx index ef1957f..9ef235f 100644 --- a/frontend/src/pages/UserPage.tsx +++ b/frontend/src/pages/UserPage.tsx @@ -10,6 +10,7 @@ import DynamicPageContent from "../components/Layout/DynamicPageContent"; import LoadingScreen from "../components/Layout/LoadingScreen"; import { StreamListItem } from "../components/Layout/ListItem"; import { CameraIcon } from "lucide-react"; +import { getCategoryThumbnail } from "../utils/thumbnailUtils"; interface UserProfileData { id: number; @@ -78,11 +79,10 @@ const UserPage: React.FC = () => { currentStreamCategory: streamData.category_id, currentStreamViewers: streamData.num_viewers, currentStreamStartTime: streamData.start_time, - currentStreamThumbnail: - streamData.thumbnail || - `/images/category_thumbnails/${streamData.category_name - .toLowerCase() - .replace(/ /g, "_")}.webp`, + currentStreamThumbnail: getCategoryThumbnail( + streamData.category_name, + streamData.thumbnail + ), }; }); let variant: "user" | "streamer" | "personal" | "admin"; diff --git a/frontend/src/utils/thumbnailUtils.ts b/frontend/src/utils/thumbnailUtils.ts new file mode 100644 index 0000000..c7e7cc3 --- /dev/null +++ b/frontend/src/utils/thumbnailUtils.ts @@ -0,0 +1,24 @@ +/** + * Generates a thumbnail path for a given category name + * + * @param categoryName - The name of the category + * @param customThumbnail - Optional custom thumbnail path that takes precedence if provided + * @returns The path to the category thumbnail image + */ +export function getCategoryThumbnail(categoryName?: string, customThumbnail?: string): string { + if (customThumbnail) { + return customThumbnail; + } + + if (!categoryName) { + return '/images/category_thumbnails/default.webp'; + } + + // Convert to lowercase, replace spaces with underscores, and remove all other special characters + const formattedName = categoryName + .toLowerCase() + .replace(/ /g, '_') // Replace spaces with underscores + .replace(/[^a-z0-9_]/g, ''); // Remove all other non-alphanumeric characters except underscores + + return `/images/category_thumbnails/${formattedName}.webp`; + } \ No newline at end of file