diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b4631d7..0017039 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,6 +1,5 @@
import { useState, useEffect } from "react";
import { AuthContext } from "./context/AuthContext";
-import { ContentProvider } from "./context/ContentContext";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import HomePage from "./pages/HomePage";
import StreamerRoute from "./components/Stream/StreamerRoute";
@@ -57,7 +56,6 @@ function App() {
setUserId,
}}
>
-
@@ -105,7 +103,6 @@ function App() {
-
);
diff --git a/frontend/src/context/ContentContext.tsx b/frontend/src/context/ContentContext.tsx
deleted file mode 100644
index 442245f..0000000
--- a/frontend/src/context/ContentContext.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import { createContext, useContext, useState, useEffect } from "react";
-import { useAuth } from "./AuthContext";
-
-// Base interfaces
-interface Item {
- id: number;
- title: string;
- viewers: number;
- thumbnail?: string;
-}
-
-interface StreamItem extends Item {
- type: "stream";
- username: string;
- streamCategory: string;
-}
-
-interface CategoryItem extends Item {
- type: "category";
-}
-
-interface UserItem extends Item {
- type: "user";
- username: string;
- isLive: boolean;
-}
-
-// Context type
-interface ContentContextType {
- streams: StreamItem[];
- categories: CategoryItem[];
- users: UserItem[];
- setStreams: (streams: StreamItem[]) => void;
- setCategories: (categories: CategoryItem[]) => void;
- setUsers: (users: UserItem[]) => void;
-}
-
-const ContentContext = createContext(undefined);
-
-export function ContentProvider({ children }: { children: React.ReactNode }) {
- const [streams, setStreams] = useState([]);
- const [categories, setCategories] = useState([]);
- const [users, setUsers] = useState([]);
- const { isLoggedIn } = useAuth();
-
- useEffect(() => {
- // Fetch streams
- 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) => ({
- type: "stream",
- id: stream.user_id,
- title: stream.title,
- username: stream.username,
- streamCategory: stream.category_name,
- viewers: stream.num_viewers,
- thumbnail:
- stream.thumbnail ||
- `/images/category_thumbnails/${stream.category_name
- .toLowerCase()
- .replace(/ /g, "_")}.webp`,
- }));
- setStreams(processedStreams);
- });
-
- // Fetch categories
- const categoriesUrl = isLoggedIn
- ? "/api/categories/recommended"
- : "/api/categories/popular/4";
- console.log("Fetching categories from", categoriesUrl);
-
- fetch(categoriesUrl)
- .then((response) => response.json())
- .then((data: any[]) => {
- 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`,
- }));
- setCategories(processedCategories);
- console.log("Categories fetched", processedCategories);
- });
- }, [isLoggedIn]);
-
- return (
-
- {children}
-
- );
-}
-
-// Custom hooks for specific content types
-export function useStreams() {
- const context = useContext(ContentContext);
- if (!context) {
- throw new Error("useStreams must be used within a ContentProvider");
- }
- return { streams: context.streams, setStreams: context.setStreams };
-}
-
-export function useCategories() {
- const context = useContext(ContentContext);
- if (!context) {
- throw new Error("useCategories must be used within a ContentProvider");
- }
- return {
- categories: context.categories,
- setCategories: context.setCategories,
- };
-}
-
-export function useUsers() {
- const context = useContext(ContentContext);
- if (!context) {
- throw new Error("useUsers must be used within a ContentProvider");
- }
- return { users: context.users, setUsers: context.setUsers };
-}
-
-// General hook for all content
-export function useContent() {
- const context = useContext(ContentContext);
- if (!context) {
- throw new Error("useContent must be used within a ContentProvider");
- }
- return context;
-}
diff --git a/frontend/src/hooks/useContent.ts b/frontend/src/hooks/useContent.ts
new file mode 100644
index 0000000..4a5d0b4
--- /dev/null
+++ b/frontend/src/hooks/useContent.ts
@@ -0,0 +1,140 @@
+// hooks/useContent.ts
+import { useState, useEffect } from "react";
+import { useAuth } from "../context/AuthContext";
+import { StreamType } from "../types/StreamType";
+import { CategoryType } from "../types/CategoryType";
+import { UserType } from "../types/UserType";
+
+// Helper function to process API data into our consistent types
+const processStreamData = (data: any[]): StreamType[] => {
+ return data.map((stream) => ({
+ type: "stream",
+ id: stream.user_id,
+ title: stream.title,
+ username: stream.username,
+ streamCategory: stream.category_name,
+ viewers: stream.num_viewers,
+ thumbnail:
+ stream.thumbnail ||
+ `/images/category_thumbnails/${stream.category_name
+ .toLowerCase()
+ .replace(/ /g, "_")}.webp`,
+ }));
+};
+
+const processCategoryData = (data: any[]): CategoryType[] => {
+ return 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`,
+ }));
+};
+
+const processUserData = (data: any[]): UserType[] => {
+ return data.map((user) => ({
+ type: "user",
+ id: user.user_id,
+ title: user.username,
+ username: user.username,
+ isLive: user.is_live,
+ viewers: 0, // This may need to be updated based on your API
+ thumbnail: user.thumbnail || "/images/pfps/default.webp",
+ }));
+};
+
+// Generic fetch hook that can be used for any content type
+export function useFetchContent(
+ url: string,
+ processor: (data: any[]) => T[],
+ dependencies: any[] = []
+): { data: T[]; isLoading: boolean; error: string | null } {
+ const [data, setData] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ setIsLoading(true);
+ try {
+ const response = await fetch(url);
+
+ if (!response.ok) {
+ throw new Error(`Error fetching data: ${response.status}`);
+ }
+
+ const rawData = await response.json();
+ const processedData = processor(rawData);
+ setData(processedData);
+ setError(null);
+ } catch (err) {
+ console.error("Error fetching content:", err);
+ setError(err instanceof Error ? err.message : "Unknown error");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchData();
+ }, dependencies);
+
+ return { data, isLoading, error };
+}
+
+// Specific hooks for each content type
+export function useStreams(customUrl?: string): {
+ streams: StreamType[];
+ isLoading: boolean;
+ error: string | null
+} {
+ const { isLoggedIn } = useAuth();
+ const url = customUrl || (isLoggedIn
+ ? "/api/streams/recommended"
+ : "/api/streams/popular/4");
+
+ const { data, isLoading, error } = useFetchContent(
+ url,
+ processStreamData,
+ [isLoggedIn, customUrl]
+ );
+
+ return { streams: data, isLoading, error };
+}
+
+export function useCategories(customUrl?: string): {
+ categories: CategoryType[];
+ isLoading: boolean;
+ error: string | null
+} {
+ const { isLoggedIn } = useAuth();
+ const url = customUrl || (isLoggedIn
+ ? "/api/categories/recommended"
+ : "/api/categories/popular/4");
+
+ const { data, isLoading, error } = useFetchContent(
+ url,
+ processCategoryData,
+ [isLoggedIn, customUrl]
+ );
+
+ return { categories: data, isLoading, error };
+}
+
+export function useUsers(customUrl?: string): {
+ users: UserType[];
+ isLoading: boolean;
+ error: string | null
+} {
+ const url = customUrl || "/api/users/popular";
+
+ const { data, isLoading, error } = useFetchContent(
+ url,
+ processUserData,
+ [customUrl]
+ );
+
+ return { users: data, isLoading, error };
+}
\ No newline at end of file
diff --git a/frontend/src/pages/CategoryPage.tsx b/frontend/src/pages/CategoryPage.tsx
index 6488bca..a87ad07 100644
--- a/frontend/src/pages/CategoryPage.tsx
+++ b/frontend/src/pages/CategoryPage.tsx
@@ -6,12 +6,12 @@ import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll";
import Button from "../components/Input/Button";
import { useAuth } from "../context/AuthContext";
import { useCategoryFollow } from "../hooks/useCategoryFollow";
-import { ListItemProps as StreamData } from "../components/Layout/ListItem";
import LoadingScreen from "../components/Layout/LoadingScreen";
+import { StreamType } from "../types/StreamType";
const CategoryPage: React.FC = () => {
const { categoryName } = useParams<{ categoryName: string }>();
- const [streams, setStreams] = useState([]);
+ const [streams, setStreams] = useState([]);
const listRowRef = useRef(null);
const isLoading = useRef(false);
const [streamOffset, setStreamOffset] = useState(0);
@@ -50,7 +50,7 @@ const CategoryPage: React.FC = () => {
setStreamOffset((prev) => prev + data.length);
- const processedStreams: StreamData[] = data.map((stream: any) => ({
+ const processedStreams: StreamType[] = data.map((stream: any) => ({
type: "stream",
id: stream.user_id,
title: stream.title,
diff --git a/frontend/src/pages/Following.tsx b/frontend/src/pages/Following.tsx
index bbf9fce..012b30e 100644
--- a/frontend/src/pages/Following.tsx
+++ b/frontend/src/pages/Following.tsx
@@ -4,6 +4,7 @@ import { useSidebar } from "../context/SidebarContext";
import { ToggleButton } from "../components/Input/Button";
import { Sidebar as SidebarIcon } from "lucide-react";
import { useNavigate } from "react-router-dom"; // Import useNavigate
+import { CategoryType } from "../types/CategoryType";
// Define TypeScript interfaces
interface Streamer {
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index 71b464f..90c331c 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -1,7 +1,7 @@
import React from "react";
import ListRow from "../components/Layout/ListRow";
import { useNavigate } from "react-router-dom";
-import { useStreams, useCategories } from "../context/ContentContext";
+import { useStreams, useCategories } from "../hooks/useContent";
import Button from "../components/Input/Button";
import DynamicPageContent from "../components/Layout/DynamicPageContent";
import LoadingScreen from "../components/Layout/LoadingScreen";
@@ -12,8 +12,8 @@ interface HomePageProps {
}
const HomePage: React.FC = ({ variant = "default" }) => {
- const { streams } = useStreams();
- const { categories } = useCategories();
+ const { streams, isLoading: isLoadingStreams } = useStreams();
+ const { categories, isLoading: isLoadingCategories } = useCategories();
const navigate = useNavigate();
const handleStreamClick = (streamerName: string) => {
@@ -24,9 +24,9 @@ const HomePage: React.FC = ({ variant = "default" }) => {
navigate(`/category/${categoryName}`);
};
- if (!categories || categories.length === 0) {
- console.log("No categories found yet");
- return Loading Categories...;
+ if (isLoadingStreams || isLoadingCategories) {
+ console.log("No content found yet");
+ return Loading Content...;
}
return (
@@ -79,7 +79,7 @@ const HomePage: React.FC = ({ variant = "default" }) => {
Show More
-
+
);
};
diff --git a/frontend/src/types/CategoryType.ts b/frontend/src/types/CategoryType.ts
new file mode 100644
index 0000000..8b3a47b
--- /dev/null
+++ b/frontend/src/types/CategoryType.ts
@@ -0,0 +1,8 @@
+// types/CategoryType.ts
+export interface CategoryType {
+ type: "category";
+ id: number;
+ title: string;
+ viewers: number;
+ thumbnail?: string;
+ }
\ No newline at end of file
diff --git a/frontend/src/types/StreamType.ts b/frontend/src/types/StreamType.ts
new file mode 100644
index 0000000..c6d4d58
--- /dev/null
+++ b/frontend/src/types/StreamType.ts
@@ -0,0 +1,9 @@
+export interface StreamType {
+ type: "stream";
+ id: number;
+ title: string;
+ username: string;
+ streamCategory: string;
+ viewers: number;
+ thumbnail?: string;
+ }
\ No newline at end of file
diff --git a/frontend/src/types/UserType.ts b/frontend/src/types/UserType.ts
new file mode 100644
index 0000000..7dee37e
--- /dev/null
+++ b/frontend/src/types/UserType.ts
@@ -0,0 +1,10 @@
+// types/UserType.ts
+export interface UserType {
+ type: "user";
+ id: number;
+ title: string;
+ username: string;
+ isLive: boolean;
+ viewers: number;
+ thumbnail?: string;
+ }
\ No newline at end of file