UPDATE/REFACTOR: Replace ContentContext with useContent hook;

REFACTOR: Add content type files;
This commit is contained in:
Chris-1010
2025-02-27 01:20:40 +00:00
parent 3f95b35acc
commit 74baa49c04
9 changed files with 178 additions and 158 deletions

View File

@@ -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,
}}
>
<ContentProvider>
<SidebarProvider>
<QuickSettingsProvider>
<BrowserRouter>
@@ -105,7 +103,6 @@ function App() {
</BrowserRouter>
</QuickSettingsProvider>
</SidebarProvider>
</ContentProvider>
</AuthContext.Provider>
</Brightness>
);

View File

@@ -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<ContentContextType | undefined>(undefined);
export function ContentProvider({ children }: { children: React.ReactNode }) {
const [streams, setStreams] = useState<StreamItem[]>([]);
const [categories, setCategories] = useState<CategoryItem[]>([]);
const [users, setUsers] = useState<UserItem[]>([]);
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 (
<ContentContext.Provider
value={{
streams,
categories,
users,
setStreams,
setCategories,
setUsers,
}}
>
{children}
</ContentContext.Provider>
);
}
// 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;
}

View File

@@ -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<T>(
url: string,
processor: (data: any[]) => T[],
dependencies: any[] = []
): { data: T[]; isLoading: boolean; error: string | null } {
const [data, setData] = useState<T[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(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<StreamType>(
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<CategoryType>(
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<UserType>(
url,
processUserData,
[customUrl]
);
return { users: data, isLoading, error };
}

View File

@@ -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<StreamData[]>([]);
const [streams, setStreams] = useState<StreamType[]>([]);
const listRowRef = useRef<any>(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,

View File

@@ -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 {

View File

@@ -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<HomePageProps> = ({ 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<HomePageProps> = ({ variant = "default" }) => {
navigate(`/category/${categoryName}`);
};
if (!categories || categories.length === 0) {
console.log("No categories found yet");
return <LoadingScreen>Loading Categories...</LoadingScreen>;
if (isLoadingStreams || isLoadingCategories) {
console.log("No content found yet");
return <LoadingScreen>Loading Content...</LoadingScreen>;
}
return (

View File

@@ -0,0 +1,8 @@
// types/CategoryType.ts
export interface CategoryType {
type: "category";
id: number;
title: string;
viewers: number;
thumbnail?: string;
}

View File

@@ -0,0 +1,9 @@
export interface StreamType {
type: "stream";
id: number;
title: string;
username: string;
streamCategory: string;
viewers: number;
thumbnail?: string;
}

View File

@@ -0,0 +1,10 @@
// types/UserType.ts
export interface UserType {
type: "user";
id: number;
title: string;
username: string;
isLive: boolean;
viewers: number;
thumbnail?: string;
}