From edb959506a35bf8515bd911b6512351f41e592a1 Mon Sep 17 00:00:00 2001 From: EvanLin3141 Date: Fri, 28 Feb 2025 19:52:01 +0000 Subject: [PATCH] ADD: All Vods on Home Page ToDo: Thumbnail in DB Save user stream to Vod --- frontend/src/components/Layout/ListItem.tsx | 51 ++++++++++++++++- frontend/src/components/Layout/ListRow.tsx | 28 ++++++++- frontend/src/hooks/useContent.ts | 38 +++++++++++++ frontend/src/pages/HomePage.tsx | 63 ++++++++++----------- frontend/src/types/VodType.ts | 12 ++++ 5 files changed, 153 insertions(+), 39 deletions(-) create mode 100644 frontend/src/types/VodType.ts diff --git a/frontend/src/components/Layout/ListItem.tsx b/frontend/src/components/Layout/ListItem.tsx index a5a7cf8..dca0440 100644 --- a/frontend/src/components/Layout/ListItem.tsx +++ b/frontend/src/components/Layout/ListItem.tsx @@ -2,13 +2,14 @@ import React from "react"; import { StreamType } from "../../types/StreamType"; import { CategoryType } from "../../types/CategoryType"; import { UserType } from "../../types/UserType"; +import { VodType } from "../../types/VodType"; // Base props that all item types share interface BaseListItemProps { onItemClick?: () => void; extraClasses?: string; } - + // Stream item component interface StreamListItemProps extends BaseListItemProps, Omit {} @@ -124,6 +125,52 @@ const UserListItem: React.FC = ({ ); }; +// VODs item component +interface VodListItemProps extends BaseListItemProps, Omit {} + +const VodListItem: React.FC = ({ + title, + streamer, + datetime, + category, + length, + views, + thumbnail, + url, + onItemClick, + extraClasses = "", +}) => { + return ( +
+
window.open(url, "_blank")} + > +
+ {thumbnail ? ( + {title} + ) : ( +
+ )} +
+
+

+ {title} +

+

{streamer}

+

{category}

+

{new Date(datetime).toLocaleDateString()} | {length} mins

+

{views} views

+
+
+
+ ); +}; + // Legacy wrapper component for backward compatibility export interface ListItemProps { type: "stream" | "category" | "user"; @@ -138,4 +185,4 @@ export interface ListItemProps { isLive?: boolean; } -export { StreamListItem, CategoryListItem, UserListItem }; \ No newline at end of file +export { StreamListItem, CategoryListItem, UserListItem, VodListItem }; \ No newline at end of file diff --git a/frontend/src/components/Layout/ListRow.tsx b/frontend/src/components/Layout/ListRow.tsx index f61293a..0f7af82 100644 --- a/frontend/src/components/Layout/ListRow.tsx +++ b/frontend/src/components/Layout/ListRow.tsx @@ -10,16 +10,17 @@ import React, { } from "react"; import { useNavigate } from "react-router-dom"; import "../../assets/styles/listRow.css"; -import { StreamListItem, CategoryListItem, UserListItem } from "./ListItem"; +import { StreamListItem, CategoryListItem, UserListItem, VodListItem } from "./ListItem"; import { StreamType } from "../../types/StreamType"; import { CategoryType } from "../../types/CategoryType"; import { UserType } from "../../types/UserType"; +import { VodType } from "../../types/VodType" -type ItemType = StreamType | CategoryType | UserType; +type ItemType = StreamType | CategoryType | UserType | VodType; interface ListRowProps { variant?: "default" | "search"; - type: "stream" | "category" | "user"; + type: "stream" | "category" | "user" | "vod"; title?: string; description?: string; items: ItemType[]; @@ -100,6 +101,9 @@ const ListRow = forwardRef((props, ref) => { const isUserType = (item: ItemType): item is UserType => item.type === "user"; + const isVodType = (item: ItemType): item is VodType => + item.type === "vod"; + return (
((props, ref) => { /> ); } + else if (type === "vod" && isVodType(item)) { + return ( + window.open(item.url, "_blank")} + extraClasses={itemExtraClasses} + /> + ); + } return null; })} diff --git a/frontend/src/hooks/useContent.ts b/frontend/src/hooks/useContent.ts index 5e99aaf..d705885 100644 --- a/frontend/src/hooks/useContent.ts +++ b/frontend/src/hooks/useContent.ts @@ -4,8 +4,28 @@ import { useAuth } from "../context/AuthContext"; import { StreamType } from "../types/StreamType"; import { CategoryType } from "../types/CategoryType"; import { UserType } from "../types/UserType"; +import { VodType } from "../types/VodType" import { getCategoryThumbnail } from "../utils/thumbnailUtils"; +// Process API data into our VodType structure +const processVodData = (data: any[]): VodType[] => { + console.log("Raw API VOD Data:", data); // Debugging + return data.map((vod) => ({ + type: "vod", + id: vod.id, // Ensure this matches API response + title: vod.title, + streamer: vod.streamer, // Ensure backend sends streamer name or ID + datetime: new Date(vod.datetime).toLocaleString(), + category: vod.category, + length: vod.length, + views: vod.views, + url: vod.url, + thumbnail: "../../images/category_thumbnails/abstract.webp", + })); +}; + + + // Helper function to process API data into our consistent types const processStreamData = (data: any[]): StreamType[] => { return data.map((stream) => ({ @@ -118,6 +138,24 @@ export function useCategories(customUrl?: string): { return { categories: data, isLoading, error }; } +export function useVods(customUrl?: string): { + vods: VodType[]; + isLoading: boolean; + error: string | null +} { + const url = customUrl || "api/vods/all"; + const { data, isLoading, error } = useFetchContent( + url, + processVodData, + [customUrl] + ); + + console.log("Fetched VODs Data:", data); // Debugging + + return { vods: data, isLoading, error }; +} + + export function useUsers(customUrl?: string): { users: UserType[]; isLoading: boolean; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 8497eec..5b79a56 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -6,25 +6,25 @@ import Button from "../components/Input/Button"; import DynamicPageContent from "../components/Layout/DynamicPageContent"; import LoadingScreen from "../components/Layout/LoadingScreen"; import Footer from "../components/Layout/Footer"; +import { useVods } from "../hooks/useContent"; // Import useVods + interface HomePageProps { variant?: "default" | "personalised"; } + const HomePage: React.FC = ({ variant = "default" }) => { const { streams, isLoading: isLoadingStreams } = useStreams(); const { categories, isLoading: isLoadingCategories } = useCategories(); + const { vods, isLoading: isLoadingVods } = useVods(); // Fetch VODs const navigate = useNavigate(); - const handleStreamClick = (streamerName: string) => { - window.location.href = `/${streamerName}`; + const handleVodClick = (vodUrl: string) => { + window.open(vodUrl, "_blank"); // Open VOD in new tab }; - const handleCategoryClick = (categoryName: string) => { - navigate(`/category/${categoryName}`); - }; - - if (isLoadingStreams || isLoadingCategories) + if (isLoadingStreams || isLoadingCategories || isLoadingVods) return Loading Content...; return ( @@ -33,52 +33,47 @@ const HomePage: React.FC = ({ variant = "default" }) => { className="relative min-h-screen animate-moving_bg" contentClassName="pb-[12vh]" > + {/* Streams Section */} navigate(`/${streamerName}`)} extraClasses="bg-[var(--liveNow)]" itemExtraClasses="w-[20vw]" /> - {/* If Personalised_HomePage, display Categories the logged-in user follows. Else, trending categories. */} + {/* Categories Section */} navigate(`/category/${categoryName}`)} titleClickable={true} extraClasses="bg-[var(--recommend)]" itemExtraClasses="w-[20vw]" > - -