diff --git a/frontend/config.tsx b/frontend/config.tsx index b92eab2..59de80b 100644 --- a/frontend/config.tsx +++ b/frontend/config.tsx @@ -1,5 +1,5 @@ export const paths = { - pfps:'', - category_thumbnails:'', - icons:'' - }; \ No newline at end of file + pfps: "", + category_thumbnails: "", + icons: "", +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b2d0c64..104c7ce 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -36,45 +36,64 @@ function App() { return ( - - - - - - - - ) : ( - - ) - } - /> - : } /> - } /> - } /> - } - > - } - > - }> - }> - } /> - } /> - - - - - - + + + + + + + + ) : ( + + ) + } + /> + + ) : ( + + ) + } + /> + } /> + } /> + } + > + } + > + } + > + }> + } /> + } /> + + + + + + ); } diff --git a/frontend/src/components/Auth/AuthModal.tsx b/frontend/src/components/Auth/AuthModal.tsx index 4e63dd7..8b10573 100644 --- a/frontend/src/components/Auth/AuthModal.tsx +++ b/frontend/src/components/Auth/AuthModal.tsx @@ -1,9 +1,6 @@ import React, { useState } from "react"; import { ToggleButton } from "../Input/Button"; -import { - LogIn as LogInIcon, - User as UserIcon, -} from "lucide-react"; +import { LogIn as LogInIcon, User as UserIcon } from "lucide-react"; import LoginForm from "./LoginForm"; import RegisterForm from "./RegisterForm"; import ForgotPasswordForm from "./ForgotPasswordForm"; @@ -93,7 +90,10 @@ const AuthModal: React.FC = ({ onClose }) => { > ✕ -
+
{authSwitch()}
@@ -104,4 +104,4 @@ const AuthModal: React.FC = ({ onClose }) => { ); }; -export default AuthModal; \ No newline at end of file +export default AuthModal; diff --git a/frontend/src/components/Auth/ForgotPasswordForm.tsx b/frontend/src/components/Auth/ForgotPasswordForm.tsx index c51678c..4cae486 100644 --- a/frontend/src/components/Auth/ForgotPasswordForm.tsx +++ b/frontend/src/components/Auth/ForgotPasswordForm.tsx @@ -4,6 +4,7 @@ import Button from "../Input/Button"; interface ForgotPasswordProps { email?: string; + general?: string; } interface SubmitProps { @@ -51,7 +52,9 @@ const ForgotPasswordForm: React.FC = ({ onSubmit }) => { if (!response.ok) { const data = await response.json(); - throw new Error(data.message || "An error has occurred while resetting"); + throw new Error( + data.message || "An error has occurred while resetting" + ); } else { confirmPasswordReset(); } @@ -68,8 +71,10 @@ const ForgotPasswordForm: React.FC = ({ onSubmit }) => { return (
-

Forgot Password

-
+

+ Forgot Password +

+
= ({ onSubmit }) => { placeholder="Enter your email" value={email} onChange={handleEmailChange} - extraClasses={`w-full mb-[1.5em] p-[0.5rem] ${errors.email ? "border-red-500" : ""}`} + extraClasses={`w-full mb-[1.5em] p-[0.5rem] ${ + errors.email ? "border-red-500" : "" + }`} />
diff --git a/frontend/src/components/Auth/LoginForm.tsx b/frontend/src/components/Auth/LoginForm.tsx index 4fb6457..0a9d88b 100644 --- a/frontend/src/components/Auth/LoginForm.tsx +++ b/frontend/src/components/Auth/LoginForm.tsx @@ -102,9 +102,10 @@ const LoginForm: React.FC = ({ onSubmit, onForgotPassword }) => { return ( <>
-

Login

+

+ Login +

- - Sign in with Google + + Sign in with Google +
diff --git a/frontend/src/components/Checkout/CheckoutForm.tsx b/frontend/src/components/Checkout/CheckoutForm.tsx index ebd10c1..3415288 100644 --- a/frontend/src/components/Checkout/CheckoutForm.tsx +++ b/frontend/src/components/Checkout/CheckoutForm.tsx @@ -21,7 +21,7 @@ export const Return: React.FC = () => { const sessionId = urlParams.get("session_id"); if (sessionId) { - console.log("1") + console.log("1"); fetch(`/api/session-status?session_id=${sessionId}`) .then((res) => res.json()) .then((data) => { diff --git a/frontend/src/components/Input/Button.tsx b/frontend/src/components/Input/Button.tsx index 1ade1a3..a4bee8b 100644 --- a/frontend/src/components/Input/Button.tsx +++ b/frontend/src/components/Input/Button.tsx @@ -23,8 +23,7 @@ const Button: React.FC = ({ ); }; -interface EditButtonProps extends ButtonProps { -} +interface EditButtonProps extends ButtonProps {} export const EditButton: React.FC = ({ children = "", @@ -39,7 +38,7 @@ export const EditButton: React.FC = ({ {children} ); -}; +}; interface ToggleButtonProps extends ButtonProps { toggled?: boolean; diff --git a/frontend/src/components/Input/Input.tsx b/frontend/src/components/Input/Input.tsx index cc6d9c7..a23929d 100644 --- a/frontend/src/components/Input/Input.tsx +++ b/frontend/src/components/Input/Input.tsx @@ -11,28 +11,26 @@ const Input: React.FC = ({ placeholder = "", value = "", extraClasses = "", - onChange = () => { }, - onKeyDown = () => { }, + onChange = () => {}, + onKeyDown = () => {}, children, ...props // all other HTML input props }) => { return ( <> -
- - -
+
+ +
- ); }; diff --git a/frontend/src/components/Layout/DynamicPageContent.tsx b/frontend/src/components/Layout/DynamicPageContent.tsx index 65c1d2f..7a47c65 100644 --- a/frontend/src/components/Layout/DynamicPageContent.tsx +++ b/frontend/src/components/Layout/DynamicPageContent.tsx @@ -9,22 +9,27 @@ interface DynamicPageContentProps { style?: React.CSSProperties; } -const DynamicPageContent: React.FC = ({ - children, +const DynamicPageContent: React.FC = ({ + children, navbarVariant = "default", className = "", - style + style, }) => { const { showSideBar } = useSidebar(); return (
-
+
{children}
); }; -export default DynamicPageContent; \ No newline at end of file +export default DynamicPageContent; diff --git a/frontend/src/components/Layout/ListItem.tsx b/frontend/src/components/Layout/ListItem.tsx index f56e6e4..6e0f569 100644 --- a/frontend/src/components/Layout/ListItem.tsx +++ b/frontend/src/components/Layout/ListItem.tsx @@ -65,7 +65,9 @@ const ListItem: React.FC = ({ )}
-

{title}

+

+ {title} +

{type === "stream" &&

{username}

} {type === "stream" && (

{streamCategory}

diff --git a/frontend/src/components/Layout/ListRow.tsx b/frontend/src/components/Layout/ListRow.tsx index d1aa663..8c66010 100644 --- a/frontend/src/components/Layout/ListRow.tsx +++ b/frontend/src/components/Layout/ListRow.tsx @@ -1,11 +1,16 @@ -import React, { forwardRef, useImperativeHandle, useRef, useState } from "react"; import { ArrowLeft as ArrowLeftIcon, ArrowRight as ArrowRightIcon, } from "lucide-react"; +import React, { + forwardRef, + useImperativeHandle, + useRef, + useState, +} from "react"; +import { useNavigate } from "react-router-dom"; import "../../assets/styles/listRow.css"; import ListItem, { ListItemProps } from "./ListItem"; -import { useNavigate } from "react-router-dom"; interface ListRowProps { variant?: "default" | "search"; @@ -14,7 +19,7 @@ interface ListRowProps { description?: string; items: ListItemProps[]; wrap?: boolean; - onClick: (itemName: string) => void; + onItemClick: (itemName: string) => void; titleClickable?: boolean; extraClasses?: string; itemExtraClasses?: string; @@ -22,8 +27,27 @@ interface ListRowProps { children?: React.ReactNode; } -const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }, ListRowProps>( - ({ variant, type, title = "", description = "", items, wrap, onClick, titleClickable, extraClasses = "", itemExtraClasses = "", amountForScroll, children }, ref) => { +const ListRow = forwardRef< + { addMoreItems: (newItems: ListItemProps[]) => void }, + ListRowProps +>( + ( + { + variant = "default", + type, + title = "", + description = "", + items, + onItemClick, + titleClickable = false, + wrap = false, + extraClasses = "", + itemExtraClasses = "", + amountForScroll = 4, + children, + }, + ref + ) => { const [currentItems, setCurrentItems] = useState(items); const slider = useRef(null); const scrollAmount = window.innerWidth * 0.3; @@ -79,7 +103,9 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void } >

handleTitleClick(type) : undefined} > @@ -90,7 +116,7 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void } {/* List Items */}
- {!wrap && currentItems.length > (amountForScroll || 0) && ( + {!wrap && currentItems.length > amountForScroll && ( <> void } onItemClick={() => (item.type === "stream" || item.type === "user") && item.username - ? onClick?.(item.username) - : onClick?.(item.title) + ? onItemClick?.(item.username) + : onItemClick?.(item.title) } - extraClasses={`${itemExtraClasses} min-w-[20vw] max-w-[20vw]`} + extraClasses={`${itemExtraClasses} w-[20vw]`} /> ))}
@@ -140,4 +166,4 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void } } ); -export default ListRow; \ No newline at end of file +export default ListRow; diff --git a/frontend/src/components/Navigation/Navbar.tsx b/frontend/src/components/Navigation/Navbar.tsx index f298f77..a605fba 100644 --- a/frontend/src/components/Navigation/Navbar.tsx +++ b/frontend/src/components/Navigation/Navbar.tsx @@ -145,12 +145,12 @@ const Navbar: React.FC = ({ variant = "default" }) => { {/* Stream Button */} - {isLoggedIn && !window.location.pathname.includes('go-live') && ( + {isLoggedIn && !window.location.pathname.includes("go-live") && (

-
-
e.currentTarget.style.boxShadow = "var(--follow-shadow)"} - onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"} +
+
+ (e.currentTarget.style.boxShadow = "var(--follow-shadow)") + } + onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} > -

Following

+

+ Following +

-

Streamers

+

+ Streamers +

    {followedStreamers.map((streamer) => (
  • = ({ extraClasses }) => {
-

Categories

+

+ Categories +

    {followedCategories.map((category) => (
  • { @@ -11,19 +11,24 @@ const QuickSettings: React.FC = () => { return (
    -
    +

    Quick Settings

    -
    +
    - - - + +
    ); }; diff --git a/frontend/src/components/Stream/ChatPanel.tsx b/frontend/src/components/Stream/ChatPanel.tsx index a9140f2..8db6924 100644 --- a/frontend/src/components/Stream/ChatPanel.tsx +++ b/frontend/src/components/Stream/ChatPanel.tsx @@ -140,12 +140,13 @@ const ChatPanel: React.FC = ({ > {/* User avatar with image */}
    msg.chatter_username === username ? null - : window.location.href = `/user/${msg.chatter_username}` + : (window.location.href = `/user/${msg.chatter_username}`) } > = ({
    {/* Username */} msg.chatter_username === username ? null - : window.location.href = `/user/${msg.chatter_username}` + : (window.location.href = `/user/${msg.chatter_username}`) } > {msg.chatter_username}
    {/* Message content */} -
    +
    {msg.message}
    {/* Time sent */}
    - {new Date(msg.time_sent).toLocaleTimeString('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit' })} + {new Date(msg.time_sent).toLocaleTimeString("en-GB", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + })}
    ))} diff --git a/frontend/src/components/Stream/StreamerRoute.tsx b/frontend/src/components/Stream/StreamerRoute.tsx index f963871..f019152 100644 --- a/frontend/src/components/Stream/StreamerRoute.tsx +++ b/frontend/src/components/Stream/StreamerRoute.tsx @@ -44,12 +44,12 @@ const StreamerRoute: React.FC = () => { if (isLive) { return ; } - + if (streamerName) { navigate(`/user/${streamerName}`); return null; } - + return
    Streamer not found
    ; }; diff --git a/frontend/src/components/Stream/Thumbnail.tsx b/frontend/src/components/Stream/Thumbnail.tsx index 90072d5..c754aec 100644 --- a/frontend/src/components/Stream/Thumbnail.tsx +++ b/frontend/src/components/Stream/Thumbnail.tsx @@ -1,5 +1,3 @@ -import React from "react"; - interface ThumbnailProps { path: string; alt?: string; diff --git a/frontend/src/components/Stream/VideoPlayer.tsx b/frontend/src/components/Stream/VideoPlayer.tsx index f694b14..1a0e4d1 100644 --- a/frontend/src/components/Stream/VideoPlayer.tsx +++ b/frontend/src/components/Stream/VideoPlayer.tsx @@ -32,7 +32,6 @@ const VideoPlayer: React.FC = ({ const setupPlayer = async () => { const streamKey = await fetchStreamKey(); const streamUrl = `/stream/${streamKey}/index.m3u8`; - console.log("Player created with src:", streamUrl); if (!playerRef.current) { const videoElement = document.createElement("video"); diff --git a/frontend/src/components/functionality/BrightnessControl.tsx b/frontend/src/components/functionality/BrightnessControl.tsx index 4166eca..52d9db2 100644 --- a/frontend/src/components/functionality/BrightnessControl.tsx +++ b/frontend/src/components/functionality/BrightnessControl.tsx @@ -4,15 +4,19 @@ import { useBrightness } from "../../context/BrightnessContext"; const BrightnessControl: React.FC = () => { const { brightness, setBrightness } = useBrightness(); - const handleBrightnessChange = (event: React.ChangeEvent) => { - {/* Set brightness based on the value. Calls BrightnessContext too */} + const handleBrightnessChange = ( + event: React.ChangeEvent + ) => { + { + /* Set brightness based on the value. Calls BrightnessContext too */ + } setBrightness(Number(event.target.value)); }; return (

    Brightness Control

    - {/* Changes based on the range of input */} + {/* Changes based on the range of input */} void; } -const BrightnessContext = createContext(undefined); +const BrightnessContext = createContext( + undefined +); -export const Brightness: React.FC<{ children: React.ReactNode }> = ({ children }) => { +export const Brightness: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { const [brightness, setBrightness] = useState(100); useEffect(() => { diff --git a/frontend/src/context/ContentContext.tsx b/frontend/src/context/ContentContext.tsx index cc138f3..ab1bf6f 100644 --- a/frontend/src/context/ContentContext.tsx +++ b/frontend/src/context/ContentContext.tsx @@ -45,40 +45,45 @@ export function ContentProvider({ children }: { children: React.ReactNode }) { useEffect(() => { // Fetch streams - const streamsUrl = isLoggedIn - ? "/api/streams/recommended" + const streamsUrl = isLoggedIn + ? "/api/streams/recommended" : "/api/streams/popular/4"; fetch(streamsUrl) .then((response) => response.json()) .then((data: any[]) => { - const processedStreams: StreamItem[] = data.map(stream => ({ + const processedStreams: StreamItem[] = data.map((stream) => ({ type: "stream", id: stream.user_id, title: stream.title, streamer: stream.username, streamCategory: stream.category_name, viewers: stream.num_viewers, - thumbnail: stream.thumbnail || - `/images/category_thumbnails/${stream.category_name.toLowerCase().replace(/ /g, "_")}.webp` + thumbnail: + stream.thumbnail || + `/images/category_thumbnails/${stream.category_name + .toLowerCase() + .replace(/ /g, "_")}.webp`, })); setStreams(processedStreams); }); // Fetch categories - const categoriesUrl = isLoggedIn - ? "/api/categories/recommended" + const categoriesUrl = isLoggedIn + ? "/api/categories/recommended" : "/api/categories/popular/4"; fetch(categoriesUrl) .then((response) => response.json()) .then((data: any[]) => { - const processedCategories: CategoryItem[] = data.map(category => ({ + const processedCategories: CategoryItem[] = data.map((category) => ({ type: "category", id: category.category_id, title: category.category_name, viewers: category.num_viewers, - thumbnail: `/images/category_thumbnails/${category.category_name.toLowerCase().replace(/ /g, "_")}.webp`, + thumbnail: `/images/category_thumbnails/${category.category_name + .toLowerCase() + .replace(/ /g, "_")}.webp`, })); setCategories(processedCategories); }); @@ -114,7 +119,10 @@ export function useCategories() { if (!context) { throw new Error("useCategories must be used within a ContentProvider"); } - return { categories: context.categories, setCategories: context.setCategories }; + return { + categories: context.categories, + setCategories: context.setCategories, + }; } export function useUsers() { diff --git a/frontend/src/context/SidebarContext.tsx b/frontend/src/context/SidebarContext.tsx index 34cdb94..579a52c 100644 --- a/frontend/src/context/SidebarContext.tsx +++ b/frontend/src/context/SidebarContext.tsx @@ -23,4 +23,4 @@ export function useSidebar() { throw new Error("useSidebar must be used within a SidebarProvider"); } return context; -} \ No newline at end of file +} diff --git a/frontend/src/context/ThemeContext.tsx b/frontend/src/context/ThemeContext.tsx index 36649aa..aaf3cfa 100644 --- a/frontend/src/context/ThemeContext.tsx +++ b/frontend/src/context/ThemeContext.tsx @@ -1,9 +1,15 @@ -import { createContext, useContext, useState, useEffect, ReactNode } from "react"; +import { + createContext, + useContext, + useState, + useEffect, + ReactNode, +} from "react"; // Defines the Theme (Colour Theme) that would be shared/used interface ThemeContextType { - theme: string; - setTheme: (theme: string) => void; + theme: string; + setTheme: (theme: string) => void; } // Store theme and provide access to setTheme function @@ -11,44 +17,45 @@ interface ThemeContextType { const ThemeContext = createContext(undefined); export const ThemeProvider = ({ children }: { children: ReactNode }) => { - // Set default theme to dark + // Set default theme to dark - const [theme, setTheme] = useState(() => { - // If exist on user cache, use that instead - return localStorage.getItem("user-theme") || "dark"; - }); + const [theme, setTheme] = useState(() => { + // If exist on user cache, use that instead + return localStorage.getItem("user-theme") || "dark"; + }); - useEffect(() => { - // Store current theme set by user - localStorage.setItem("user-theme", theme); + useEffect(() => { + // Store current theme set by user + localStorage.setItem("user-theme", theme); - // Update the theme - document.body.setAttribute("data-theme", theme); - }, [theme]); + // Update the theme + document.body.setAttribute("data-theme", theme); + }, [theme]); - return ( - // Sets the selected theme to child component - - {children} - - ); + return ( + // Sets the selected theme to child component + + {children} + + ); }; // Custom Hook which allows any component to access theme & setTheme with "useTheme()" export const useTheme = () => { - const context = useContext(ThemeContext); //Retrieves current value of context - if (!context) { - throw new Error("useTheme must be used within a ThemeProvider"); //If called outside of ThemeContext.tsx, errorHandle - } - return context; + const context = useContext(ThemeContext); //Retrieves current value of context + if (!context) { + throw new Error("useTheme must be used within a ThemeProvider"); //If called outside of ThemeContext.tsx, errorHandle + } + return context; }; - -{/** +{ + /** createContext: Allow components to share data without directly passing props through multiple levels useContext: Allows a component to access the current value of a context ("Hook") useState: Manages state of a component ("Hook") ReactNode: Allows to take in HTML / React / Arrays of Component - */} \ No newline at end of file + */ +} diff --git a/frontend/src/hooks/useCategoryFollow.ts b/frontend/src/hooks/useCategoryFollow.ts index bce79db..f304ef6 100644 --- a/frontend/src/hooks/useCategoryFollow.ts +++ b/frontend/src/hooks/useCategoryFollow.ts @@ -15,7 +15,7 @@ export function useCategoryFollow() { } }; - const followCategory = async (categoryName: number) => { + const followCategory = async (categoryName: string) => { if (!isLoggedIn) { return; } @@ -34,7 +34,7 @@ export function useCategoryFollow() { } }; - const unfollowCategory = async (categoryName: number) => { + const unfollowCategory = async (categoryName: string) => { if (!isLoggedIn) { return; } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 1c3ce53..cac94ca 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,6 +5,6 @@ import App from "./App.tsx"; createRoot(document.getElementById("root")!).render( - + ); diff --git a/frontend/src/pages/AllCategoriesPage.tsx b/frontend/src/pages/AllCategoriesPage.tsx index 257aed0..b585fad 100644 --- a/frontend/src/pages/AllCategoriesPage.tsx +++ b/frontend/src/pages/AllCategoriesPage.tsx @@ -17,29 +17,31 @@ const AllCategoriesPage: React.FC = () => { 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}`); + 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 []; } - setCategoryOffset(prev => prev + data.length); + setCategoryOffset((prev) => prev + data.length); const processedCategories = data.map((category: any) => ({ type: "category" as const, @@ -51,7 +53,7 @@ const AllCategoriesPage: React.FC = () => { .replace(/ /g, "_")}.webp`, })); - setCategories(prev => [...prev, ...processedCategories]); + setCategories((prev) => [...prev, ...processedCategories]); return processedCategories; } catch (error) { console.error("Error fetching categories:", error); @@ -99,7 +101,7 @@ const AllCategoriesPage: React.FC = () => { type="category" title="All Categories" items={categories} - onClick={handleCategoryClick} + onItemClick={handleCategoryClick} extraClasses="bg-[var(--recommend)] text-center" wrap={true} /> @@ -107,4 +109,4 @@ const AllCategoriesPage: React.FC = () => { ); }; -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 4464f9c..1c2e745 100644 --- a/frontend/src/pages/CategoryPage.tsx +++ b/frontend/src/pages/CategoryPage.tsx @@ -6,16 +6,7 @@ import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll"; import Button from "../components/Input/Button"; import { useAuth } from "../context/AuthContext"; import { useCategoryFollow } from "../hooks/useCategoryFollow"; - -interface StreamData { - type: "stream"; - id: number; - title: string; - streamer: string; - streamCategory: string; - viewers: number; - thumbnail?: string; -} +import { ListItemProps as StreamData } from "../components/Layout/ListItem"; const CategoryPage: React.FC = () => { const { categoryName } = useParams<{ categoryName: string }>(); @@ -26,10 +17,15 @@ const CategoryPage: React.FC = () => { const [noStreams, setNoStreams] = useState(12); const [hasMoreData, setHasMoreData] = useState(true); const { isLoggedIn } = useAuth(); - const { isCategoryFollowing, checkCategoryFollowStatus, followCategory, unfollowCategory } = useCategoryFollow() + const { + isCategoryFollowing, + checkCategoryFollowStatus, + followCategory, + unfollowCategory, + } = useCategoryFollow(); useEffect(() => { - checkCategoryFollowStatus(categoryName); + if (categoryName) checkCategoryFollowStatus(categoryName); }, [categoryName]); const fetchCategoryStreams = async () => { @@ -38,7 +34,9 @@ const CategoryPage: React.FC = () => { isLoading.current = true; try { - const response = await fetch(`/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}`); + const response = await fetch( + `/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}` + ); if (!response.ok) { throw new Error("Failed to fetch category streams"); } @@ -49,13 +47,13 @@ const CategoryPage: React.FC = () => { 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, - streamer: stream.username, + username: stream.username, streamCategory: categoryName, viewers: stream.num_viewers, thumbnail: @@ -66,8 +64,8 @@ const CategoryPage: React.FC = () => { .replace(/ /g, "_")}.webp`), })); - setStreams(prev => [...prev, ...processedStreams]); - return processedStreams + setStreams((prev) => [...prev, ...processedStreams]); + return processedStreams; } catch (error) { console.error("Error fetching category streams:", error); } finally { @@ -90,7 +88,6 @@ const CategoryPage: React.FC = () => { fetchContentOnScroll(logOnScroll, hasMoreData); - const handleStreamClick = (streamerName: string) => { window.location.href = `/${streamerName}`; }; @@ -115,14 +112,18 @@ const CategoryPage: React.FC = () => { description={`Live streams in the ${categoryName} category`} items={streams} wrap={true} - onClick={handleStreamClick} + onItemClick={handleStreamClick} extraClasses="bg-[var(--recommend)]" > {isLoggedIn && (