diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index baeeb97..96deb94 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,8 @@ import { Brightness } from "./context/BrightnessContext"; import LoadingScreen from "./components/Layout/LoadingScreen"; import Following from "./pages/Following"; import UnsubscribePage from "./pages/UnsubscribePage"; +import Vods from "./pages/Vods"; +import VodPlayer from "./pages/VodPlayer"; function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); @@ -77,6 +79,8 @@ function App() { }> } /> } /> + } /> + } /> } /> diff --git a/frontend/src/pages/UserPage.tsx b/frontend/src/pages/UserPage.tsx index f97a394..f4d12ce 100644 --- a/frontend/src/pages/UserPage.tsx +++ b/frontend/src/pages/UserPage.tsx @@ -278,9 +278,8 @@ const UserPage: React.FC = () => { onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "var(--follow-shadow)")} onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} > -
    -
  • Streamers
  • -
+ +
{ + const params = useParams<{ vod_id?: string; username?: string }>(); + const vod_id = params.vod_id || "unknown"; + const username = params.username || "unknown"; + + const videoUrl = `/vods/${username}/${vod_id}.mp4`; + + return ( +
+

Watching VOD {vod_id}

+ +
+ ); +}; + +export default VodPlayer; diff --git a/frontend/src/pages/Vods.tsx b/frontend/src/pages/Vods.tsx new file mode 100644 index 0000000..970eaa6 --- /dev/null +++ b/frontend/src/pages/Vods.tsx @@ -0,0 +1,92 @@ +import React, { useEffect, useState } from "react"; +import { useAuth } from "../context/AuthContext"; +import { useNavigate, useParams } from "react-router-dom"; +import DynamicPageContent from "../components/Layout/DynamicPageContent"; + +interface Vod { + vod_id: number; + title: string; + datetime: string; + username: string; + category_name: string; + length: number; + views: number; +} + +const Vods: React.FC = () => { + const navigate = useNavigate(); + const { username } = useParams<{ username?: string }>(); + const { isLoggedIn } = useAuth(); + const [ownedVods, setOwnedVods] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!username) return; + + const fetchVods = async () => { + try { + const response = await fetch(`/api/vods/${username}`); + if (!response.ok) throw new Error(`Failed to fetch VODs: ${response.statusText}`); + + const data = await response.json(); + setOwnedVods(data); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Error fetching VODs."; + setError(errorMessage); + } finally { + setLoading(false); + } + }; + + fetchVods(); + }, [username]); + + if (loading) return

Loading VODs...

; + if (error) return

{error}

; + + return ( + +
+

{username}'s VODs

+
+ {ownedVods.length === 0 ? ( +

No VODs available.

+ ) : ( + ownedVods.map((vod) => { + const thumbnailUrl = `/stream/${username}/vods/${vod.vod_id}.png`; + + return ( +
navigate(`/stream/${username}/vods/${vod.vod_id}`)} + > + {/* Thumbnail */} + {`Thumbnail { + e.currentTarget.onerror = null; + e.currentTarget.src = "/default-thumbnail.png"; + }} + /> + + {/* Video Info */} +

{vod.title}

+

📅 {new Date(vod.datetime).toLocaleString()}

+

🎮 {vod.category_name}

+

⏱ {Math.floor(vod.length / 60)} min

+

👀 {vod.views} views

+
+ ); + }) + )} +
+
+
+ ); +}; + +export default Vods;