UPDATE: Move user status fetch to App.tsx;
REFACTOR: Rename `Vods` & `Following` to `VodsPage` & `FollowingPage` and improve structure; UPDATE: Change default profile picture; REFACTOR: Remove VOD testing data; UPDATE: Change `VideoPlayer` atrributes;
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 139 KiB |
@@ -14,9 +14,9 @@ import { QuickSettingsProvider } from "./context/QuickSettingsContext";
|
|||||||
import DashboardPage from "./pages/DashboardPage";
|
import DashboardPage from "./pages/DashboardPage";
|
||||||
import { Brightness } from "./context/BrightnessContext";
|
import { Brightness } from "./context/BrightnessContext";
|
||||||
import LoadingScreen from "./components/Layout/LoadingScreen";
|
import LoadingScreen from "./components/Layout/LoadingScreen";
|
||||||
import Following from "./pages/Following";
|
import Following from "./pages/FollowingPage";
|
||||||
import UnsubscribePage from "./pages/UnsubscribePage";
|
import UnsubscribePage from "./pages/UnsubscribePage";
|
||||||
import Vods from "./pages/Vods";
|
import VodsPage from "./pages/VodsPage";
|
||||||
import VodPlayer from "./pages/VodPlayer";
|
import VodPlayer from "./pages/VodPlayer";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -44,6 +44,24 @@ function App() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoggedIn) {
|
||||||
|
checkUserStatus();
|
||||||
|
}
|
||||||
|
}, [username]);
|
||||||
|
|
||||||
|
const checkUserStatus = async () => {
|
||||||
|
if (!username) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user/${username}/status`);
|
||||||
|
const data = await response.json();
|
||||||
|
setIsLive(data.is_live);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking user status:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
@@ -79,8 +97,8 @@ function App() {
|
|||||||
<Route path="/results" element={<ResultsPage />}></Route>
|
<Route path="/results" element={<ResultsPage />}></Route>
|
||||||
<Route path="/404" element={<NotFoundPage />} />
|
<Route path="/404" element={<NotFoundPage />} />
|
||||||
<Route path="/user/:username/following" element={<Following />} />
|
<Route path="/user/:username/following" element={<Following />} />
|
||||||
<Route path="/user/:username/vods" element={<Vods />} />
|
<Route path="/vods/:username" element={<VodsPage />} />
|
||||||
<Route path="/stream/:username/vods/:vod_id" element={<VodPlayer />} />
|
<Route path="/vods/:username/:vod_id" element={<VodPlayer />} />
|
||||||
<Route path="*" element={<Navigate to="/404" replace />} />
|
<Route path="*" element={<Navigate to="/404" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
@@ -360,11 +360,9 @@ const StreamDashboard: React.FC<StreamDashboardProps> = ({ username, userId, isL
|
|||||||
<h2 className="text-center text-2xl font-bold text-white mb-4">Stream Preview</h2>
|
<h2 className="text-center text-2xl font-bold text-white mb-4">Stream Preview</h2>
|
||||||
<div className="flex flex-col gap-4 bg-gray-800 rounded-lg p-4 w-full h-fit flex-grow justify-around">
|
<div className="flex flex-col gap-4 bg-gray-800 rounded-lg p-4 w-full h-fit flex-grow justify-around">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-white text-center pb-4">Video</p>
|
|
||||||
<VideoPlayer streamer={username ?? undefined} extraClasses="border border-white" onStreamDetected={setStreamDetected} />
|
<VideoPlayer streamer={username ?? undefined} extraClasses="border border-white" onStreamDetected={setStreamDetected} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-white text-center">List Item</p>
|
|
||||||
<StreamListItem
|
<StreamListItem
|
||||||
id={1}
|
id={1}
|
||||||
title={streamData.title || "Stream Title"}
|
title={streamData.title || "Stream Title"}
|
||||||
|
|||||||
@@ -32,12 +32,13 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ streamer, extraClasses = "",
|
|||||||
const videoElement = document.createElement("video");
|
const videoElement = document.createElement("video");
|
||||||
videoElement.classList.add("video-js", "vjs-big-play-centered", "w-full", "h-full");
|
videoElement.classList.add("video-js", "vjs-big-play-centered", "w-full", "h-full");
|
||||||
videoElement.setAttribute("playsinline", "true");
|
videoElement.setAttribute("playsinline", "true");
|
||||||
|
videoElement.setAttribute('preload', 'auto');
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
videoRef.current.appendChild(videoElement);
|
videoRef.current.appendChild(videoElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
playerRef.current = videojs(videoElement, {
|
playerRef.current = videojs(videoElement, {
|
||||||
controls: false,
|
controls: true,
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
muted: false,
|
muted: false,
|
||||||
fluid: true,
|
fluid: true,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface DashboardPageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DashboardPage: React.FC<DashboardPageProps> = ({ tab = "dashboard" }) => {
|
const DashboardPage: React.FC<DashboardPageProps> = ({ tab = "dashboard" }) => {
|
||||||
const { username, isLive, userId, setIsLive } = useAuth();
|
const { username, isLive, userId } = useAuth();
|
||||||
const { vods } = useVods(`/api/vods/${username}`);
|
const { vods } = useVods(`/api/vods/${username}`);
|
||||||
const [selectedTab, setSelectedTab] = useState<"dashboard" | "stream" | "vod">(tab);
|
const [selectedTab, setSelectedTab] = useState<"dashboard" | "stream" | "vod">(tab);
|
||||||
|
|
||||||
@@ -21,24 +21,6 @@ const DashboardPage: React.FC<DashboardPageProps> = ({ tab = "dashboard" }) => {
|
|||||||
dashboard: "white",
|
dashboard: "white",
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (username) {
|
|
||||||
checkUserStatus();
|
|
||||||
}
|
|
||||||
}, [username]);
|
|
||||||
|
|
||||||
const checkUserStatus = async () => {
|
|
||||||
if (!username) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/user/${username}/status`);
|
|
||||||
const data = await response.json();
|
|
||||||
setIsLive(data.is_live);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error checking user status:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicPageContent className="flex flex-col min-h-screen bg-gradient-radial from-purple-600 via-blue-900 to-black">
|
<DynamicPageContent className="flex flex-col min-h-screen bg-gradient-radial from-purple-600 via-blue-900 to-black">
|
||||||
<div id="dashboard" className="flex flex-col justify-evenly items-center h-full text-white">
|
<div id="dashboard" className="flex flex-col justify-evenly items-center h-full text-white">
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
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<Vod[]>([]);
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [error, setError] = useState<string | null>(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 <p className="text-center">Loading VODs...</p>;
|
|
||||||
if (error) return <p className="text-center text-red-500">{error}</p>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DynamicPageContent className="h-full">
|
|
||||||
<div className="mt-[3em] w-screen flex justify-center">
|
|
||||||
<div
|
|
||||||
id="vods-container"
|
|
||||||
className="w-[96vw] bg-slate-50/35 rounded-lg p-4 overflow-x-auto whitespace-nowrap scrollbar-hide pb-7"
|
|
||||||
>
|
|
||||||
<h1 className="text-2xl font-bold text-center mb-4">{username}'s VODs</h1>
|
|
||||||
|
|
||||||
{/* Horizontal Scrollable VOD List */}
|
|
||||||
<div className="flex space-x-4 overflow-x-auto scrollbar-hide">
|
|
||||||
{ownedVods.length === 0 ? (
|
|
||||||
<p className="text-center w-full">No VODs available.</p>
|
|
||||||
) : (
|
|
||||||
ownedVods.map((vod) => {
|
|
||||||
const thumbnailUrl = `/vods/${username}/${vod.vod_id}.png`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={vod.vod_id}
|
|
||||||
className="w-[30.8vw] h-[30vh] flex-shrink-0 bg-gray-800 text-white rounded-lg p-3 cursor-pointer hover:bg-gray-700 transition"
|
|
||||||
onClick={() => navigate(`/stream/${username}/vods/${vod.vod_id}`)}
|
|
||||||
>
|
|
||||||
{/* Thumbnail */}
|
|
||||||
<img
|
|
||||||
src={thumbnailUrl}
|
|
||||||
alt={`Thumbnail for ${vod.title}`}
|
|
||||||
className="w-full h-[150px] object-cover rounded-md"
|
|
||||||
onError={(e) => {
|
|
||||||
e.currentTarget.onerror = null;
|
|
||||||
e.currentTarget.src = "/default-thumbnail.png";
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Video Info */}
|
|
||||||
<div className="mt-2">
|
|
||||||
<h2 className="text-lg font-bold">{vod.title}</h2>
|
|
||||||
<p className="text-sm text-gray-300">{username}</p>
|
|
||||||
<p className="text-sm text-gray-400">{vod.views} views</p>
|
|
||||||
<p className="text-xs text-gray-400">{new Date(vod.datetime).toLocaleString()}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DynamicPageContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Vods;
|
|
||||||
36
frontend/src/pages/VodsPage.tsx
Normal file
36
frontend/src/pages/VodsPage.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import DynamicPageContent from "../components/Layout/DynamicPageContent";
|
||||||
|
import { useVods } from "../hooks/useContent";
|
||||||
|
import ListRow from "../components/Layout/ListRow";
|
||||||
|
import LoadingScreen from "../components/Layout/LoadingScreen";
|
||||||
|
|
||||||
|
const VodsPage: React.FC = () => {
|
||||||
|
const { username } = useParams<{ username?: string }>();
|
||||||
|
const { vods, isLoading, error } = useVods(`/api/vods/${username}`);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
if (isLoading) return <LoadingScreen>Loading VODs...</LoadingScreen>;
|
||||||
|
if (error) return <LoadingScreen>Error loading VODs: {error}</LoadingScreen>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DynamicPageContent className="h-full">
|
||||||
|
<div className="mt-[3em] flex justify-center">
|
||||||
|
<div id="vods-container" className="w-[96vw] bg-slate-50/35 rounded-lg p-4 overflow-x-auto whitespace-nowrap scrollbar-hide pb-7">
|
||||||
|
<h1 className="text-2xl font-bold text-center mb-4"></h1>
|
||||||
|
<ListRow
|
||||||
|
type="vod"
|
||||||
|
title={`${username}'s VODs`}
|
||||||
|
items={vods}
|
||||||
|
wrap={true}
|
||||||
|
onItemClick={(user, vodId) => navigate(`/vods/${user}/${vodId}`)}
|
||||||
|
extraClasses="bg-black/50"
|
||||||
|
itemExtraClasses="w-[30vw]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DynamicPageContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VodsPage;
|
||||||
@@ -129,14 +129,6 @@ INSERT INTO streams (user_id, title, start_time, category_id) VALUES
|
|||||||
(26, 'Max Level, Max Power!', '2025-03-09 21:00:00', 26),
|
(26, 'Max Level, Max Power!', '2025-03-09 21:00:00', 26),
|
||||||
(27, 'Final Showdown!', '2025-03-10 17:00:00', 27);
|
(27, 'Final Showdown!', '2025-03-10 17:00:00', 27);
|
||||||
|
|
||||||
-- Sample Data for vods
|
|
||||||
INSERT INTO vods (user_id, title, datetime, category_id, length, views) VALUES
|
|
||||||
(1, 'Epic Gaming Session', '2025-01-23 18:00:00', 1, 120, 500),
|
|
||||||
(2, 'Live Music Jam', '2025-01-21 20:00:00', 2, 180, 800),
|
|
||||||
(3, 'Sketching Live', '2025-01-22 15:00:00', 3, 90, 300),
|
|
||||||
(4, 'Math Made Easy', '2025-01-21 10:00:00', 4, 150, 600),
|
|
||||||
(5, 'Sports Highlights', '2025-01-19 12:00:00', 5, 210, 700);
|
|
||||||
|
|
||||||
-- Sample Data for tags
|
-- Sample Data for tags
|
||||||
INSERT INTO tags(tag_name) VALUES
|
INSERT INTO tags(tag_name) VALUES
|
||||||
('English'),
|
('English'),
|
||||||
|
|||||||
Reference in New Issue
Block a user