UPDATE/REFACTOR: Replace ContentContext with useContent hook;
REFACTOR: Add content type files;
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { AuthContext } from "./context/AuthContext";
|
import { AuthContext } from "./context/AuthContext";
|
||||||
import { ContentProvider } from "./context/ContentContext";
|
|
||||||
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
||||||
import HomePage from "./pages/HomePage";
|
import HomePage from "./pages/HomePage";
|
||||||
import StreamerRoute from "./components/Stream/StreamerRoute";
|
import StreamerRoute from "./components/Stream/StreamerRoute";
|
||||||
@@ -57,7 +56,6 @@ function App() {
|
|||||||
setUserId,
|
setUserId,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContentProvider>
|
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<QuickSettingsProvider>
|
<QuickSettingsProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
@@ -105,7 +103,6 @@ function App() {
|
|||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</QuickSettingsProvider>
|
</QuickSettingsProvider>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</ContentProvider>
|
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
</Brightness>
|
</Brightness>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
140
frontend/src/hooks/useContent.ts
Normal file
140
frontend/src/hooks/useContent.ts
Normal 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 };
|
||||||
|
}
|
||||||
@@ -6,12 +6,12 @@ import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll";
|
|||||||
import Button from "../components/Input/Button";
|
import Button from "../components/Input/Button";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
import { useCategoryFollow } from "../hooks/useCategoryFollow";
|
import { useCategoryFollow } from "../hooks/useCategoryFollow";
|
||||||
import { ListItemProps as StreamData } from "../components/Layout/ListItem";
|
|
||||||
import LoadingScreen from "../components/Layout/LoadingScreen";
|
import LoadingScreen from "../components/Layout/LoadingScreen";
|
||||||
|
import { StreamType } from "../types/StreamType";
|
||||||
|
|
||||||
const CategoryPage: React.FC = () => {
|
const CategoryPage: React.FC = () => {
|
||||||
const { categoryName } = useParams<{ categoryName: string }>();
|
const { categoryName } = useParams<{ categoryName: string }>();
|
||||||
const [streams, setStreams] = useState<StreamData[]>([]);
|
const [streams, setStreams] = useState<StreamType[]>([]);
|
||||||
const listRowRef = useRef<any>(null);
|
const listRowRef = useRef<any>(null);
|
||||||
const isLoading = useRef(false);
|
const isLoading = useRef(false);
|
||||||
const [streamOffset, setStreamOffset] = useState(0);
|
const [streamOffset, setStreamOffset] = useState(0);
|
||||||
@@ -50,7 +50,7 @@ const CategoryPage: React.FC = () => {
|
|||||||
|
|
||||||
setStreamOffset((prev) => prev + data.length);
|
setStreamOffset((prev) => prev + data.length);
|
||||||
|
|
||||||
const processedStreams: StreamData[] = data.map((stream: any) => ({
|
const processedStreams: StreamType[] = data.map((stream: any) => ({
|
||||||
type: "stream",
|
type: "stream",
|
||||||
id: stream.user_id,
|
id: stream.user_id,
|
||||||
title: stream.title,
|
title: stream.title,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useSidebar } from "../context/SidebarContext";
|
|||||||
import { ToggleButton } from "../components/Input/Button";
|
import { ToggleButton } from "../components/Input/Button";
|
||||||
import { Sidebar as SidebarIcon } from "lucide-react";
|
import { Sidebar as SidebarIcon } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom"; // Import useNavigate
|
import { useNavigate } from "react-router-dom"; // Import useNavigate
|
||||||
|
import { CategoryType } from "../types/CategoryType";
|
||||||
|
|
||||||
// Define TypeScript interfaces
|
// Define TypeScript interfaces
|
||||||
interface Streamer {
|
interface Streamer {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ListRow from "../components/Layout/ListRow";
|
import ListRow from "../components/Layout/ListRow";
|
||||||
import { useNavigate } from "react-router-dom";
|
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 Button from "../components/Input/Button";
|
||||||
import DynamicPageContent from "../components/Layout/DynamicPageContent";
|
import DynamicPageContent from "../components/Layout/DynamicPageContent";
|
||||||
import LoadingScreen from "../components/Layout/LoadingScreen";
|
import LoadingScreen from "../components/Layout/LoadingScreen";
|
||||||
@@ -12,8 +12,8 @@ interface HomePageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||||
const { streams } = useStreams();
|
const { streams, isLoading: isLoadingStreams } = useStreams();
|
||||||
const { categories } = useCategories();
|
const { categories, isLoading: isLoadingCategories } = useCategories();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleStreamClick = (streamerName: string) => {
|
const handleStreamClick = (streamerName: string) => {
|
||||||
@@ -24,9 +24,9 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
|||||||
navigate(`/category/${categoryName}`);
|
navigate(`/category/${categoryName}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!categories || categories.length === 0) {
|
if (isLoadingStreams || isLoadingCategories) {
|
||||||
console.log("No categories found yet");
|
console.log("No content found yet");
|
||||||
return <LoadingScreen>Loading Categories...</LoadingScreen>;
|
return <LoadingScreen>Loading Content...</LoadingScreen>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -79,7 +79,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
|||||||
Show More
|
Show More
|
||||||
</Button>
|
</Button>
|
||||||
</ListRow>
|
</ListRow>
|
||||||
<Footer/>
|
<Footer />
|
||||||
</DynamicPageContent>
|
</DynamicPageContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
8
frontend/src/types/CategoryType.ts
Normal file
8
frontend/src/types/CategoryType.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// types/CategoryType.ts
|
||||||
|
export interface CategoryType {
|
||||||
|
type: "category";
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
viewers: number;
|
||||||
|
thumbnail?: string;
|
||||||
|
}
|
||||||
9
frontend/src/types/StreamType.ts
Normal file
9
frontend/src/types/StreamType.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface StreamType {
|
||||||
|
type: "stream";
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
username: string;
|
||||||
|
streamCategory: string;
|
||||||
|
viewers: number;
|
||||||
|
thumbnail?: string;
|
||||||
|
}
|
||||||
10
frontend/src/types/UserType.ts
Normal file
10
frontend/src/types/UserType.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user