From 1499e042cbd50efc8dd9a778d34350f83115a148 Mon Sep 17 00:00:00 2001 From: Chris-1010 <122332721@umail.ucc.ie> Date: Thu, 6 Feb 2025 19:47:25 +0000 Subject: [PATCH] FIX: General fixes and update to HomePage --- frontend/index.html | 2 +- .../src/components/Checkout/CheckoutForm.tsx | 4 +- frontend/src/components/Layout/ListRow.tsx | 74 +++++++++---------- frontend/src/components/Layout/Navbar.tsx | 1 + .../src/components/Stream/StreamerRoute.tsx | 4 +- frontend/src/context/StreamsContext.tsx | 4 +- frontend/src/pages/HomePage.tsx | 8 +- frontend/src/pages/UserPage.tsx | 28 +++++-- frontend/src/pages/VideoPage.tsx | 25 +++---- frontend/tailwind.config.js | 12 +-- web_server/blueprints/__init__.py | 3 +- web_server/blueprints/streams.py | 12 ++- web_server/blueprints/user.py | 12 +-- web_server/blueprints/utils.py | 11 --- web_server/database/testing_data.sql | 27 +++---- web_server/utils/stream_utils.py | 2 +- web_server/utils/user_utils.py | 2 +- 17 files changed, 116 insertions(+), 115 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 5766ebe..2f8fa8d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,7 +7,7 @@ Team Software Project -
+
diff --git a/frontend/src/components/Checkout/CheckoutForm.tsx b/frontend/src/components/Checkout/CheckoutForm.tsx index d759d58..c304bbd 100644 --- a/frontend/src/components/Checkout/CheckoutForm.tsx +++ b/frontend/src/components/Checkout/CheckoutForm.tsx @@ -21,7 +21,7 @@ export const Return: React.FC = () => { const sessionId = urlParams.get("session_id"); if (sessionId) { - fetch(`${API_URL}/session-status?session_id=${sessionId}`) + fetch(`/api/session-status?session_id=${sessionId}`) .then((res) => res.json()) .then((data) => { setStatus(data.status); @@ -56,7 +56,7 @@ interface CheckoutFormProps { const CheckoutForm: React.FC = ({ onClose }) => { const fetchClientSecret = () => { - return fetch(`${API_URL}/create-checkout-session`, { + return fetch(`/api/create-checkout-session`, { method: "POST", }) .then((res) => res.json()) diff --git a/frontend/src/components/Layout/ListRow.tsx b/frontend/src/components/Layout/ListRow.tsx index 8a227ef..538da67 100644 --- a/frontend/src/components/Layout/ListRow.tsx +++ b/frontend/src/components/Layout/ListRow.tsx @@ -19,40 +19,6 @@ interface ListRowProps { onClick: (itemId: number, itemName: string) => void; } -// Individual list entry component -const ListItem: React.FC = ({ - type, - title, - streamer, - viewers, - thumbnail, - onItemClick, -}) => { - return ( -
-
- {thumbnail ? ( - {title} - ) : ( -
- )} -
-
-

{title}

- {type === "stream" &&

{streamer}

} -

{viewers} viewers

-
-
- ); -}; - // Row of entries const ListRow: React.FC = ({ title, @@ -62,10 +28,10 @@ const ListRow: React.FC = ({ extraClasses="", }) => { return ( -
+
-

{title}

-

{description}

+

{title}

+

{description}

{items.map((item) => ( @@ -85,4 +51,38 @@ const ListRow: React.FC = ({ ); }; +// Individual list entry component +const ListItem: React.FC = ({ + type, + title, + streamer, + viewers, + thumbnail, + onItemClick, +}) => { + return ( +
+
+ {thumbnail ? ( + {title} + ) : ( +
+ )} +
+
+

{title}

+ {type === "stream" &&

{streamer}

} +

{viewers} viewers

+
+
+ ); +}; + export default ListRow; diff --git a/frontend/src/components/Layout/Navbar.tsx b/frontend/src/components/Layout/Navbar.tsx index 1f5c46a..2515fea 100644 --- a/frontend/src/components/Layout/Navbar.tsx +++ b/frontend/src/components/Layout/Navbar.tsx @@ -23,6 +23,7 @@ const Navbar: React.FC = ({ const [showAuthModal, setShowAuthModal] = useState(false); const { isLoggedIn } = useAuth(); const [showSideBar, setShowSideBar] = useState(false); + useEffect(() => { if (showAuthModal) { document.body.style.overflow = "hidden"; diff --git a/frontend/src/components/Stream/StreamerRoute.tsx b/frontend/src/components/Stream/StreamerRoute.tsx index b8f21c0..c8747c6 100644 --- a/frontend/src/components/Stream/StreamerRoute.tsx +++ b/frontend/src/components/Stream/StreamerRoute.tsx @@ -12,7 +12,7 @@ const StreamerRoute: React.FC = () => { useEffect(() => { const checkStreamStatus = async () => { try { - const response = await fetch(`/api/streamer/${streamerName}/status`); + const response = await fetch(`/api/user/${streamerName}/status`); const data = await response.json(); setIsLive(Boolean(data.is_live)); setStreamId(data.most_recent_stream); @@ -44,7 +44,7 @@ const StreamerRoute: React.FC = () => { // streamId=0 is a special case for the streamer's latest stream return isLive ? ( - + ) : streamerName ? ( ) : ( diff --git a/frontend/src/context/StreamsContext.tsx b/frontend/src/context/StreamsContext.tsx index 0fdff33..603d823 100644 --- a/frontend/src/context/StreamsContext.tsx +++ b/frontend/src/context/StreamsContext.tsx @@ -34,7 +34,7 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) { const { isLoggedIn } = useAuth(); const fetch_url = isLoggedIn - ? ["/api/streams/recommended", "/api/categories/following"] + ? ["/api/streams/recommended", "/api/categories/recommended"] : ["/api/streams/popular/4", "/api/categories/popular/4"]; useEffect(() => { @@ -44,7 +44,7 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) { .then((data: StreamItem[]) => { const extractedData: StreamItem[] = data.map((stream: any) => ({ type: "stream", - id: stream.stream_id, + id: stream.user_id, title: stream.title, streamer: stream.username, viewers: stream.num_viewers, diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 3ba9e2f..e593f43 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -12,8 +12,8 @@ const HomePage: React.FC = ({ variant = "default" }) => { const { featuredStreams, featuredCategories } = useStreams(); const navigate = useNavigate(); - const handleStreamClick = (streamId: number, streamerName: string) => { - console.log(`Navigating to stream ${streamId}`); + const handleStreamClick = (streamerId: number, streamerName: string) => { + console.log(`Navigating to stream by user ${streamerId}`); navigate(`/${streamerName}`); }; @@ -43,7 +43,7 @@ const HomePage: React.FC = ({ variant = "default" }) => { } items={featuredStreams} onClick={handleStreamClick} - extraClasses="border border-gray-700 bg-[#FF7F50]/80" + extraClasses="bg-red-950/60" /> {/* If Personalised_HomePage, display Categories the logged-in user follows. Else, trending categories. */} @@ -61,7 +61,7 @@ const HomePage: React.FC = ({ variant = "default" }) => { } items={featuredCategories} onClick={handleCategoryClick} - extraClasses="border border-gray-700 bg-[#5AFF75]/80" + extraClasses="bg-green-950/60" />
diff --git a/frontend/src/pages/UserPage.tsx b/frontend/src/pages/UserPage.tsx index 748fcef..7fd2f4c 100644 --- a/frontend/src/pages/UserPage.tsx +++ b/frontend/src/pages/UserPage.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import Navbar from "../components/Layout/Navbar"; import { useParams } from "react-router-dom"; +import { useAuth } from "../context/AuthContext"; interface UserProfileData { username: string; @@ -11,19 +12,28 @@ interface UserProfileData { const UserPage: React.FC = () => { const [profileData, setProfileData] = useState(null); + const { username: loggedInUsername } = useAuth(); const { username } = useParams(); + let userPageVariant = "user"; + + let setUserPageVariant = (currentStream: string) => { + if (username === loggedInUsername) userPageVariant = "personal"; + else if (currentStream) userPageVariant = "streamer"; + }; useEffect(() => { // Fetch user profile data - fetch(`/api/get_streamer_data/${username}`) + fetch(`/api/user/${username}`) .then((res) => res.json()) .then((data) => { setProfileData({ username: data.username, bio: data.bio || "This user hasn't written a bio yet.", - followerCount: data.num_followering || 0, + followerCount: data.num_followers || 0, isPartnered: data.isPartnered || false, }); + + setUserPageVariant(data.current_stream_title); }) .catch((err) => console.error("Error fetching profile data:", err)); }, [username]); @@ -42,9 +52,12 @@ const UserPage: React.FC = () => {
{/* Profile Section - Left Third */} -
- {/* Profile Picture */} +
+ {/* Profile Picture */}
{ )}
+ {/* Username & Follower Count */}

{profileData.username}

+ {userPageVariant.toUpperCase()}
@@ -111,7 +126,10 @@ const UserPage: React.FC = () => {
{/* Content Section */} -
+

Past Broadcasts

No past broadcasts found diff --git a/frontend/src/pages/VideoPage.tsx b/frontend/src/pages/VideoPage.tsx index 3d86ed9..dc2f646 100644 --- a/frontend/src/pages/VideoPage.tsx +++ b/frontend/src/pages/VideoPage.tsx @@ -8,23 +8,21 @@ import VideoPlayer from "../components/Video/VideoPlayer"; import { SocketProvider } from "../context/SocketContext"; interface VideoPageProps { - streamId: number; + streamerId: number; } interface StreamDataProps { - streamId: number; streamTitle: string; streamerName: string; - streamerId: number; startTime: string; categoryName: string; } -const VideoPage: React.FC = ({ streamId }) => { +const VideoPage: React.FC = ({ streamerId }) => { const { isLoggedIn } = useAuth(); const { streamerName } = useParams<{ streamerName: string }>(); const [streamData, setStreamData] = useState(); - const [viewerCount, setViewerCount] = useState(1000000); + const [viewerCount, setViewerCount] = useState(0); const [isChatOpen, setIsChatOpen] = useState(true); // const [showCheckout, setShowCheckout] = useState(false); // const showReturn = window.location.search.includes("session_id"); @@ -44,11 +42,7 @@ const VideoPage: React.FC = ({ streamId }) => { // }, [showCheckout]); useEffect(() => { // Fetch stream data for this streamer - fetch( - `/api/get_stream_data/${streamerName}${ - streamId == 0 ? "" : `/${streamId}` - }` - ).then((res) => { + fetch(`/api/streams/${streamerId}/data`).then((res) => { if (!res.ok) { console.error("Failed to load stream data:", res.statusText); } @@ -57,9 +51,7 @@ const VideoPage: React.FC = ({ streamId }) => { .then((data) => { // Transform snake_case to camelCase const transformedData: StreamDataProps = { - streamId: streamId, streamerName: data.username, - streamerId: data.user_id, streamTitle: data.title, startTime: data.start_time, categoryName: data.category_name, @@ -70,7 +62,7 @@ const VideoPage: React.FC = ({ streamId }) => { console.error("Error fetching stream data:", error); }); }); - }, [streamId, streamerName]); + }, [streamerId]); const toggleChat = () => { setIsChatOpen((prev) => !prev); @@ -88,7 +80,7 @@ const VideoPage: React.FC = ({ streamId }) => { } grid-rows-[auto_1fr] bg-gray-900 h-full grid-cols-[auto_25vw] transition-all`} >
- +
= ({ streamId }) => { {isChatOpen ? "Hide Chat" : "Show Chat"} - setViewerCount(count)} /> + setViewerCount(count)} + />
list[dict]: streams = get_streams_based_on_category(category) return streams -@stream_bp.route('/streams//data') -def get_stream(streamer_username): +@stream_bp.route('/streams//data') +def get_stream_data(streamer_id): """ - Returns a streamer's most recent stream data + Returns a streamer's current stream data """ - - user_id = get_user_id(streamer_username) - return jsonify(get_most_recent_stream(user_id)) + return jsonify(get_current_stream_data(streamer_id)) ## Category Routes diff --git a/web_server/blueprints/user.py b/web_server/blueprints/user.py index 6cd41c5..6662d58 100644 --- a/web_server/blueprints/user.py +++ b/web_server/blueprints/user.py @@ -1,4 +1,4 @@ -from flask import Blueprint, jsonify, session, abort, abort +from flask import Blueprint, jsonify, session from utils.user_utils import * from blueprints.utils import login_required from blueprints.email import send_email, forgot_password_body @@ -10,14 +10,14 @@ r = redis.from_url(redis_url, decode_responses=True) user_bp = Blueprint("user", __name__) @user_bp.route('/user/') -def get_user_data_(username): +def get_user_data(username): """ Returns a given user's data """ user_id = get_user_id(username) if not user_id: - abort(404) - data = get_user_data(user_id) + jsonify({"error": "User not found from username"}), 404 + data = get_user(user_id) return jsonify(data) ## Subscription Routes @@ -127,5 +127,5 @@ def user_reset_password(token, new_password): if response: return 200 else: - abort(500) - return abort(404) \ No newline at end of file + return jsonify({"error": "Failed to reset password"}), 500 + return jsonify({"error": "Invalid token"}), 400 \ No newline at end of file diff --git a/web_server/blueprints/utils.py b/web_server/blueprints/utils.py index 30fe6f1..b808fdb 100644 --- a/web_server/blueprints/utils.py +++ b/web_server/blueprints/utils.py @@ -1,25 +1,14 @@ from flask import redirect, url_for, request, g, session from functools import wraps from re import match -from time import time def logged_in_user(): """ Validator to make sure a user is logged in. """ - g.start_time = time() g.user = session.get("username", None) - print(f"Path: {request.path}, session username: {g.user}", flush=True) g.admin = session.get("username", None) -def record_time(response): - if hasattr(g, 'start_time'): - time_taken = time() - g.start_time - print(f"Request to {request.endpoint} took {time_taken:.4f} seconds", flush=True) - else: - print("No start time found", flush=True) - return response - def login_required(view): """ Add at start of routes where users need to be logged in to access. diff --git a/web_server/database/testing_data.sql b/web_server/database/testing_data.sql index 212b889..442fbd3 100644 --- a/web_server/database/testing_data.sql +++ b/web_server/database/testing_data.sql @@ -94,18 +94,6 @@ INSERT INTO chat (stream_id, chatter_id, message) VALUES (1, 2, 'Woah, cannot believe that'); -SELECT * FROM users; -SELECT * FROM follows; -SELECT * FROM user_preferences; -SELECT * FROM subscribes; -SELECT * FROM categories; -SELECT * FROM streams; -SELECT * FROM chat; -SELECT * FROM tags; -SELECT * FROM stream_tags; - --- To see all tables in the database -SELECT name FROM sqlite_master WHERE type='table'; SELECT users.user_id, streams.title, streams.num_viewers, users.username FROM streams JOIN users @@ -133,4 +121,17 @@ INSERT INTO followed_categories (user_id, category_id) VALUES INSERT INTO subscribes (user_id, subscribed_id, since, expires) VALUES (7, 1, '2024-08-30', '2025-02-28 12:00:00'), -(7, 2, '2024-08-30', '2025-02-15'); \ No newline at end of file +(7, 2, '2024-08-30', '2025-02-15'); + +SELECT * FROM users; +SELECT * FROM follows; +SELECT * FROM user_preferences; +SELECT * FROM subscribes; +SELECT * FROM categories; +SELECT * FROM streams; +SELECT * FROM chat; +SELECT * FROM tags; +SELECT * FROM stream_tags; + +-- To see all tables in the database +SELECT name FROM sqlite_master WHERE type='table'; diff --git a/web_server/utils/stream_utils.py b/web_server/utils/stream_utils.py index fa312db..2984c70 100644 --- a/web_server/utils/stream_utils.py +++ b/web_server/utils/stream_utils.py @@ -34,7 +34,7 @@ def get_followed_live_streams(user_id: int) -> Optional[List[dict]]: """, (user_id,)) return live_streams -def get_most_recent_stream(user_id: int) -> Optional[dict]: +def get_current_stream_data(user_id: int) -> Optional[dict]: """ Returns data of the most recent stream by a streamer """ diff --git a/web_server/utils/user_utils.py b/web_server/utils/user_utils.py index 13d57f6..cb55c9f 100644 --- a/web_server/utils/user_utils.py +++ b/web_server/utils/user_utils.py @@ -195,7 +195,7 @@ def get_followed_streamers(user_id: int) -> Optional[List[dict]]: """, (user_id,)) return followed_streamers -def get_user_data(user_id: int) -> Optional[dict]: +def get_user(user_id: int) -> Optional[dict]: """ Returns username, bio, number of followers, and if user is partnered from user_id """