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 { Brightness } from "./context/BrightnessContext";
|
||||
import LoadingScreen from "./components/Layout/LoadingScreen";
|
||||
import Following from "./pages/Following";
|
||||
import Following from "./pages/FollowingPage";
|
||||
import UnsubscribePage from "./pages/UnsubscribePage";
|
||||
import Vods from "./pages/Vods";
|
||||
import VodsPage from "./pages/VodsPage";
|
||||
import VodPlayer from "./pages/VodPlayer";
|
||||
|
||||
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) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
@@ -79,8 +97,8 @@ function App() {
|
||||
<Route path="/results" element={<ResultsPage />}></Route>
|
||||
<Route path="/404" element={<NotFoundPage />} />
|
||||
<Route path="/user/:username/following" element={<Following />} />
|
||||
<Route path="/user/:username/vods" element={<Vods />} />
|
||||
<Route path="/stream/:username/vods/:vod_id" element={<VodPlayer />} />
|
||||
<Route path="/vods/:username" element={<VodsPage />} />
|
||||
<Route path="/vods/:username/:vod_id" element={<VodPlayer />} />
|
||||
<Route path="*" element={<Navigate to="/404" replace />} />
|
||||
</Routes>
|
||||
</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>
|
||||
<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">
|
||||
<p className="text-white text-center pb-4">Video</p>
|
||||
<VideoPlayer streamer={username ?? undefined} extraClasses="border border-white" onStreamDetected={setStreamDetected} />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="text-white text-center">List Item</p>
|
||||
<StreamListItem
|
||||
id={1}
|
||||
title={streamData.title || "Stream Title"}
|
||||
|
||||
@@ -32,12 +32,13 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ streamer, extraClasses = "",
|
||||
const videoElement = document.createElement("video");
|
||||
videoElement.classList.add("video-js", "vjs-big-play-centered", "w-full", "h-full");
|
||||
videoElement.setAttribute("playsinline", "true");
|
||||
videoElement.setAttribute('preload', 'auto');
|
||||
if (videoRef.current) {
|
||||
videoRef.current.appendChild(videoElement);
|
||||
}
|
||||
|
||||
playerRef.current = videojs(videoElement, {
|
||||
controls: false,
|
||||
controls: true,
|
||||
autoplay: true,
|
||||
muted: false,
|
||||
fluid: true,
|
||||
|
||||
@@ -11,7 +11,7 @@ interface DashboardPageProps {
|
||||
}
|
||||
|
||||
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 [selectedTab, setSelectedTab] = useState<"dashboard" | "stream" | "vod">(tab);
|
||||
|
||||
@@ -21,24 +21,6 @@ const DashboardPage: React.FC<DashboardPageProps> = ({ tab = "dashboard" }) => {
|
||||
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 (
|
||||
<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">
|
||||
|
||||
@@ -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),
|
||||
(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
|
||||
INSERT INTO tags(tag_name) VALUES
|
||||
('English'),
|
||||
|
||||
Reference in New Issue
Block a user