-
-
- Search results for "{query}"
-
-
+ useEffect(() => {
+ // If location state changes, update our internal state
+ if (location.state) {
+ setSearchState({
+ searchResults: location.state.searchResults,
+ query: location.state.query,
+ });
+ }
+ }, [location.state]);
-
-
({
- id: stream.user_id,
- type: "stream",
- title: stream.title,
- username: stream.username,
- streamCategory: stream.category_name,
- viewers: stream.num_viewers,
- thumbnail: stream.thumbnail_url,
- }))}
- title="Streams"
- onItemClick={(streamer_name: string) =>
- (window.location.href = `/${streamer_name}`)
- }
- extraClasses="min-h-[calc((20vw+20vh)/4)] bg-[var(--liveNow)]"
- itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
- amountForScroll={3}
- />
+ return (
+
+
+
Search results for "{searchState.query}"
+
handleSearchResults(results, query)} />
- ({
- id: category.category_id,
- type: "category",
- title: category.category_name,
- viewers: 0,
- thumbnail: getCategoryThumbnail(category.category_name),
- }))}
- title="Categories"
- onItemClick={(category_name: string) =>
- navigate(`/category/${category_name}`)
- }
- titleClickable={true}
- extraClasses="min-h-[calc((20vw+20vh)/4)] bg-[var(--liveNow)]"
- itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
- amountForScroll={3}
- />
+
+ ({
+ id: stream.user_id,
+ type: "stream",
+ title: stream.title,
+ username: stream.username,
+ streamCategory: stream.category_name,
+ viewers: stream.num_viewers,
+ thumbnail: stream.thumbnail_url,
+ }))}
+ title="Streams"
+ onItemClick={(streamer_name: string) => (window.location.href = `/${streamer_name}`)}
+ extraClasses="min-h-[calc((20vw+20vh)/4)] bg-[var(--liveNow)]"
+ itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
+ amountForScroll={3}
+ />
- ({
- id: user.user_id,
- type: "user",
- title: `${user.is_live ? "🔴" : ""} ${user.username}`,
- username: user.username
- }))}
- title="Users"
- onItemClick={(username: string) =>
- (window.location.href = `/user/${username}`)
- }
- amountForScroll={3}
- extraClasses="min-h-[calc((20vw+20vh)/4)] bg-[var(--liveNow)]"
- itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
- />
-
+ ({
+ id: category.category_id,
+ type: "category",
+ title: category.category_name,
+ viewers: 0,
+ thumbnail: getCategoryThumbnail(category.category_name),
+ }))}
+ title="Categories"
+ onItemClick={(category_name: string) => navigate(`/category/${category_name}`)}
+ titleClickable={true}
+ extraClasses="min-h-[calc((20vw+20vh)/4)] bg-[var(--liveNow)]"
+ itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
+ amountForScroll={3}
+ />
-
-
-
-
- );
+ ({
+ id: user.user_id,
+ type: "user",
+ title: `${user.is_live ? "🔴" : ""} ${user.username}`,
+ username: user.username,
+ }))}
+ title="Users"
+ onItemClick={(username: string) => (window.location.href = `/user/${username}`)}
+ amountForScroll={3}
+ extraClasses="min-h-[calc((20vw+20vh)/4)] bg-[var(--liveNow)]"
+ itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
+ />
+
+
+
+ );
};
export default ResultsPage;
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")}
>
- {
+ 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 */}
+

{
+ 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;