diff --git a/frontend/src/components/Layout/ListItem.tsx b/frontend/src/components/Layout/ListItem.tsx index 2fb8713..9967149 100644 --- a/frontend/src/components/Layout/ListItem.tsx +++ b/frontend/src/components/Layout/ListItem.tsx @@ -84,7 +84,11 @@ const UserListItem: React.FC = ({ title, username, isLive, on onClick={onItemClick} > { + e.currentTarget.src = "/images/pfps/default.png"; + e.currentTarget.onerror = null; + }} alt={`user ${username}`} className="rounded-xl border-[0.15em] border-[var(--bg-color)] cursor-pointer" /> diff --git a/frontend/src/components/Navigation/Sidebar.tsx b/frontend/src/components/Navigation/Sidebar.tsx index 00be08f..1451a3d 100644 --- a/frontend/src/components/Navigation/Sidebar.tsx +++ b/frontend/src/components/Navigation/Sidebar.tsx @@ -4,199 +4,164 @@ import { useNavigate } from "react-router-dom"; import { useAuth } from "../../context/AuthContext"; import { useSidebar } from "../../context/SidebarContext"; import { ToggleButton } from "../Input/Button"; +import { getCategoryThumbnail } from "../../utils/thumbnailUtils"; interface Streamer { - user_id: number; - username: string; + user_id: number; + username: string; } interface Category { - category_id: number; - category_name: string; + category_id: number; + category_name: string; } interface SideBarProps { - extraClasses?: string; + extraClasses?: string; } const Sidebar: React.FC = ({ extraClasses = "" }) => { - const { showSideBar, setShowSideBar } = useSidebar(); - const navigate = useNavigate(); - const { username, isLoggedIn } = useAuth(); - const [followedStreamers, setFollowedStreamers] = useState([]); - const [followedCategories, setFollowedCategories] = useState([]); - const [justToggled, setJustToggled] = useState(false); - const sidebarId = useRef(Math.floor(Math.random() * 1000000)); + const { showSideBar, setShowSideBar } = useSidebar(); + const navigate = useNavigate(); + const { username, isLoggedIn } = useAuth(); + const [followedStreamers, setFollowedStreamers] = useState([]); + const [followedCategories, setFollowedCategories] = useState([]); + const [justToggled, setJustToggled] = useState(false); + const sidebarId = useRef(Math.floor(Math.random() * 1000000)); - // Fetch followed streamers & categories - useEffect(() => { - if (!isLoggedIn) return; + // Fetch followed streamers & categories + useEffect(() => { + if (!isLoggedIn) return; - const fetchFollowData = async () => { - try { - const response = await fetch("/api/user/following"); - if (!response.ok) throw new Error("Failed to fetch followed content"); - const data = await response.json(); - setFollowedStreamers(data.streams); - setFollowedCategories(data.categories); - } catch (error) { - console.error("Error fetching followed content:", error); - } - }; + const fetchFollowData = async () => { + try { + const response = await fetch("/api/user/following"); + if (!response.ok) throw new Error("Failed to fetch followed content"); + const data = await response.json(); + setFollowedStreamers(data.streams); + setFollowedCategories(data.categories); + } catch (error) { + console.error("Error fetching followed content:", error); + } + }; - fetchFollowData(); - }, [isLoggedIn]); + fetchFollowData(); + }, [isLoggedIn]); - const handleSideBar = () => { - setShowSideBar(!showSideBar); - setJustToggled(true); - setTimeout(() => setJustToggled(false), 200); - }; + const handleSideBar = () => { + setShowSideBar(!showSideBar); + setJustToggled(true); + setTimeout(() => setJustToggled(false), 200); + }; - // Keyboard shortcut to toggle sidebar - useEffect(() => { - const handleKeyPress = (e: KeyboardEvent) => { - if ( - e.key === "s" && - document.activeElement == document.body && - isLoggedIn - ) - handleSideBar(); - }; + // Keyboard shortcut to toggle sidebar + useEffect(() => { + const handleKeyPress = (e: KeyboardEvent) => { + if (e.key === "s" && document.activeElement == document.body && isLoggedIn) handleSideBar(); + }; - document.addEventListener("keydown", handleKeyPress); + document.addEventListener("keydown", handleKeyPress); - return () => { - document.removeEventListener("keydown", handleKeyPress); - }; - }, [showSideBar, setShowSideBar, isLoggedIn]); + return () => { + document.removeEventListener("keydown", handleKeyPress); + }; + }, [showSideBar, setShowSideBar, isLoggedIn]); - return ( - <> - handleSideBar()} - extraClasses={`absolute group text-[1rem] top-[9vh] ${ - showSideBar - ? "left-[16vw] duration-[0.5s]" - : "left-[20px] duration-[1s]" - } ease-in-out cursor-pointer z-[50]`} - toggled={showSideBar} - > - + return ( + <> + handleSideBar()} + extraClasses={`absolute group text-[1rem] top-[9vh] ${ + showSideBar ? "left-[16vw] duration-[0.5s]" : "left-[20px] duration-[1s]" + } ease-in-out cursor-pointer z-[50]`} + toggled={showSideBar} + > + - {!showSideBar && !justToggled && ( - - Press S - - )} - -
- {/* Profile Info */} -
- profile picture navigate(`/user/${username}`)} - /> -
-
- Logged in as -
- -
-
+ {!showSideBar && !justToggled && ( + + Press S + + )} + +
+ {/* Profile Info */} +
+ { + e.currentTarget.src = "/images/pfps/default.png"; + e.currentTarget.onerror = null; + }} + alt="profile picture" + className="w-[3em] h-[3em] rounded-full border-[0.15em] border-purple-500 cursor-pointer" + onClick={() => navigate(`/user/${username}`)} + /> +
+
Logged in as
+ +
+
-
-
- (e.currentTarget.style.boxShadow = "var(--follow-shadow)") - } - onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} - > -

- Following -

-
-
-

- Streamers -

-
- {followedStreamers.map((streamer) => ( - - ))} -
-
+
+
(e.currentTarget.style.boxShadow = "var(--follow-shadow)")} + onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} + > +

Following

+
+
+

Streamers

+
+ {followedStreamers.map((streamer) => ( + + ))} +
+
-
-

- Categories -

+
+

Categories

- {/* Followed Categories */} -
- {followedCategories.map((category) => { - return ( -
+ {followedCategories.map((category) => { + return ( +
- (window.location.href = `/category/${category.category_name}`) - } - > - {category.category_name} -
- {category.category_name} -
-
- ); - })} -
-
-
-
- - ); + onClick={() => (window.location.href = `/category/${category.category_name}`)} + > + {category.category_name} +
+ {category.category_name} +
+
+ ); + })} +
+
+
+ + + ); }; export default Sidebar; diff --git a/frontend/src/components/Stream/ChatPanel.tsx b/frontend/src/components/Stream/ChatPanel.tsx index 42e51f7..1d14040 100644 --- a/frontend/src/components/Stream/ChatPanel.tsx +++ b/frontend/src/components/Stream/ChatPanel.tsx @@ -183,7 +183,11 @@ const ChatPanel: React.FC = ({ streamId, onViewerCountChange }) onClick={() => (msg.chatter_username === username ? null : (window.location.href = `/user/${msg.chatter_username}`))} > { + e.currentTarget.src = "/images/pfps/default.png"; + e.currentTarget.onerror = null; + }} alt="User Avatar" className="w-full h-full object-cover" style={{ width: "2.5em", height: "2.5em" }} diff --git a/frontend/src/pages/Following.tsx b/frontend/src/pages/Following.tsx index 445432d..6566110 100644 --- a/frontend/src/pages/Following.tsx +++ b/frontend/src/pages/Following.tsx @@ -7,8 +7,8 @@ import { useAuthModal } from "../hooks/useAuthModal"; import FollowUserButton from "../components/Input/FollowUserButton"; interface Streamer { - user_id: number; - username: string; + user_id: number; + username: string; } interface FollowingStreamerProps { @@ -45,10 +45,10 @@ const Following: React.FC = ({ extraClasses = "" }) => { } }; - if (isLoggedIn) { - fetchFollowedStreamers(); - } - }, [isLoggedIn]); + if (isLoggedIn) { + fetchFollowedStreamers(); + } + }, [isLoggedIn]); const handleFollowToggle = async (userId: number) => { diff --git a/frontend/src/pages/ResultsPage.tsx b/frontend/src/pages/ResultsPage.tsx index 300c9b1..bb3addc 100644 --- a/frontend/src/pages/ResultsPage.tsx +++ b/frontend/src/pages/ResultsPage.tsx @@ -84,9 +84,7 @@ const ResultsPage: React.FC = ({ }) => { id: user.user_id, type: "user", title: `${user.is_live ? "🔴" : ""} ${user.username}`, - viewers: 0, - username: user.username, - thumbnail: user.profile_picture, + username: user.username }))} title="Users" onItemClick={(username: string) => diff --git a/frontend/src/pages/UserPage.tsx b/frontend/src/pages/UserPage.tsx index fe15f5d..aac3c9c 100644 --- a/frontend/src/pages/UserPage.tsx +++ b/frontend/src/pages/UserPage.tsx @@ -14,343 +14,295 @@ import { getCategoryThumbnail } from "../utils/thumbnailUtils"; import { useSameUser } from "../hooks/useSameUser"; interface UserProfileData { - id: number; - username: string; - bio: string; - followerCount: number; - isPartnered: boolean; - isLive: boolean; - currentStreamTitle?: string; - currentStreamCategory?: string; - currentStreamViewers?: number; - currentStreamStartTime?: string; - currentStreamThumbnail?: string; + id: number; + username: string; + bio: string; + followerCount: number; + isPartnered: boolean; + isLive: boolean; + currentStreamTitle?: string; + currentStreamCategory?: string; + currentStreamViewers?: number; + currentStreamStartTime?: string; + currentStreamThumbnail?: string; } const UserPage: React.FC = () => { - const [userPageVariant, setUserPageVariant] = useState< - "personal" | "streamer" | "user" | "admin" - >("user"); - const [profileData, setProfileData] = useState(); - const { isFollowing, checkFollowStatus, followUser, unfollowUser } = - useFollow(); - const { showAuthModal, setShowAuthModal } = useAuthModal(); - const { username: loggedInUsername } = useAuth(); - const { username } = useParams(); - const isUser = useSameUser({username}); - const navigate = useNavigate(); + const [userPageVariant, setUserPageVariant] = useState<"personal" | "streamer" | "user" | "admin">("user"); + const [profileData, setProfileData] = useState(); + const { isFollowing, checkFollowStatus, followUser, unfollowUser } = useFollow(); + const { showAuthModal, setShowAuthModal } = useAuthModal(); + const { username: loggedInUsername } = useAuth(); + const { username } = useParams(); + const isUser = useSameUser({ username }); + const navigate = useNavigate(); - // Saves uploaded image as profile picture for the user - const saveUploadedImage = async (event) => { - const img = event.target.files[0]; - if (img) { - const formData = new FormData(); - formData.append('image', img); - - try { - const response = await fetch('/api/user/profile_picture/upload', { - method: 'POST', - body: formData, - }); - - if (response.ok) { - console.log("Success"); - } - } catch (error) { - console.log("Failure"); - } - } - }; + // Saves uploaded image as profile picture for the user + const saveUploadedImage = async (event: React.ChangeEvent) => { + const files = event.target.files; + if (!files) return; + const img = files[0]; + if (img) { + const formData = new FormData(); + formData.append("image", img); - useEffect(() => { - // Fetch user profile data - fetch(`/api/user/${username}`) - .then((res) => res.json()) - .then((data) => { - setProfileData({ - id: data.user_id, - username: data.username, - bio: data.bio || "This user hasn't written a bio yet.", - followerCount: data.num_followers || 0, - isPartnered: data.isPartnered || false, - isLive: data.is_live, - currentStreamTitle: "", - currentStreamCategory: "", - currentStreamViewers: 0, - currentStreamThumbnail: "", - }); + try { + const response = await fetch("/api/user/profile_picture/upload", { + method: "POST", + body: formData, + }); - if (data.is_live) { - // Fetch stream data for this streamer - fetch(`/api/streams/${data.user_id}/data`) - .then((res) => res.json()) - .then((streamData) => { - setProfileData((prevData) => { - if (!prevData) return prevData; - return { - ...prevData, - currentStreamTitle: streamData.title, - currentStreamCategory: streamData.category_id, - currentStreamViewers: streamData.num_viewers, - currentStreamStartTime: streamData.start_time, - currentStreamThumbnail: getCategoryThumbnail( - streamData.category_name, - streamData.thumbnail - ), - }; - }); - let variant: "user" | "streamer" | "personal" | "admin"; - if (username === loggedInUsername) variant = "personal"; - else if (streamData.title) variant = "streamer"; - // else if (data.isAdmin) variant = "admin"; - else variant = "user"; - setUserPageVariant(variant); - }) - .catch((err) => console.error("Error fetching stream data:", err)); - } - }) - .catch((err) => { - console.error("Error fetching profile data:", err); - navigate("/404"); - }); + if (response.ok) { + console.log("Success"); + window.location.reload(); + } + } catch (error) { + console.log("Failure"); + } + } + }; - // Check if the *logged-in* user is following this user - if (loggedInUsername && username) checkFollowStatus(username); - }, [username]); + useEffect(() => { + // Fetch user profile data + fetch(`/api/user/${username}`) + .then((res) => res.json()) + .then((data) => { + setProfileData({ + id: data.user_id, + username: data.username, + bio: data.bio || "This user hasn't written a bio yet.", + followerCount: data.num_followers || 0, + isPartnered: data.isPartnered || false, + isLive: data.is_live, + currentStreamTitle: "", + currentStreamCategory: "", + currentStreamViewers: 0, + currentStreamThumbnail: "", + }); - if (!profileData) return ; - console.log(isUser) - return ( - -
-
- {/* Profile Section - TOP */} + if (data.is_live) { + // Fetch stream data for this streamer + fetch(`/api/streams/${data.user_id}/data`) + .then((res) => res.json()) + .then((streamData) => { + setProfileData((prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + currentStreamTitle: streamData.title, + currentStreamCategory: streamData.category_id, + currentStreamViewers: streamData.num_viewers, + currentStreamStartTime: streamData.start_time, + currentStreamThumbnail: getCategoryThumbnail(streamData.category_name, streamData.thumbnail), + }; + }); + let variant: "user" | "streamer" | "personal" | "admin"; + if (username === loggedInUsername) variant = "personal"; + else if (streamData.title) variant = "streamer"; + // else if (data.isAdmin) variant = "admin"; + else variant = "user"; + setUserPageVariant(variant); + }) + .catch((err) => console.error("Error fetching stream data:", err)); + } + }) + .catch((err) => { + console.error("Error fetching profile data:", err); + navigate("/404"); + }); -
+
+
+ {/* Profile Section - TOP */} + +
- {/* Border Overlay (Always on Top) */} -
+ > + {/* Border Overlay (Always on Top) */} +
- {/* Background Box */} -
- {/*
*/} -
- {/* Profile Picture */} -
+ {/*
*/} +
+ {/* Profile Picture */} +
-
+ {/* If current user is the profile user then allow profile picture swap */} + {isUser && ( + <> +
+
+ +
+ + + )} + +
- {/* Username - Now Directly Below PFP */} -

- {profileData.username} -

+ {/* Username - Now Directly Below PFP */} +

+ {profileData.username} +

- {/* Follower Count */} - {userPageVariant === "streamer" && ( - <> -
- - {profileData.followerCount.toLocaleString()} followers - - {profileData.isPartnered && ( - - Partner - - )} -
+ {/* Follower Count */} + {userPageVariant === "streamer" && ( + <> +
+ {profileData.followerCount.toLocaleString()} followers + {profileData.isPartnered && Partner} +
- {/* (Un)Follow Button */} - {!isFollowing ? ( - - ) : ( - - )} - - )} -
+ {/* (Un)Follow Button */} + {!isFollowing ? ( + + ) : ( + + )} + + )} +
-
- {/* User Type (e.g., "USER") */} - - {userPageVariant.toUpperCase()} - +
+ {/* User Type (e.g., "USER") */} + {userPageVariant.toUpperCase()} -
-

- About {profileData.username} -

-

- {profileData.bio} -

-
-
+
+

About {profileData.username}

+

{profileData.bio}

+
+
- {/* Content Section */} -
- {userPageVariant === "streamer" && ( - <> - {profileData.isLive ? ( -
-

- Currently Live! -

- { - navigate(`/${profileData.username}`); - }} - /> -
- ) : ( -

Currently not live

- )} + {/* Content Section */} +
+ {userPageVariant === "streamer" && ( + <> + {profileData.isLive ? ( +
+

Currently Live!

+ { + navigate(`/${profileData.username}`); + }} + /> +
+ ) : ( +

Currently not live

+ )} - {/* ↓↓ VODS ↓↓ */} -
-

Past Broadcasts

-
- No past broadcasts found -
-
- - )} + {/* ↓↓ VODS ↓↓ */} +
+

Past Broadcasts

+
No past broadcasts found
+
+ + )} - {userPageVariant === "user" && ( - <> - {/* ↓↓ VODS ↓↓ */} -
-

Past Broadcasts

-
- No past broadcasts found -
-
- - )} -
+ {userPageVariant === "user" && ( + <> + {/* ↓↓ VODS ↓↓ */} +
+

Past Broadcasts

+
No past broadcasts found
+
+ + )} +
-
-
- (e.currentTarget.style.boxShadow = "var(--follow-shadow)") - } - onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} - > - -
-
(e.currentTarget.style.boxShadow = "none")} + > + +
+
- (e.currentTarget.style.boxShadow = "var(--follow-shadow)") - } - onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} - > -
    -
  • - Streamers -
  • -
-
-
(e.currentTarget.style.boxShadow = "none")} + > +
    +
  • Streamers
  • +
+
+
- (e.currentTarget.style.boxShadow = "var(--follow-shadow)") - } - onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} - > - -
-
-
-
- {showAuthModal && setShowAuthModal(false)} />} - - ); + onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "var(--follow-shadow)")} + onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} + > + +
+ + + + {showAuthModal && setShowAuthModal(false)} />} +
+ ); }; export default UserPage; diff --git a/frontend/src/pages/VideoPage.tsx b/frontend/src/pages/VideoPage.tsx index 32183af..e000c17 100644 --- a/frontend/src/pages/VideoPage.tsx +++ b/frontend/src/pages/VideoPage.tsx @@ -16,245 +16,228 @@ import { StreamType } from "../types/StreamType"; const CheckoutForm = lazy(() => import("../components/Checkout/CheckoutForm")); interface VideoPageProps { - streamerId: number; + streamerId: number; } const VideoPage: React.FC = ({ streamerId }) => { - const { isLoggedIn } = useAuth(); - const { streamerName } = useParams<{ streamerName: string }>(); - const [streamData, setStreamData] = useState(); - const [viewerCount, setViewerCount] = useState(0); - const { showSideBar } = useSidebar(); - const { isFollowing, checkFollowStatus, followUser, unfollowUser } = - useFollow(); - const { showAuthModal, setShowAuthModal } = useAuthModal(); - const [isStripeReady, setIsStripeReady] = useState(false); - const [showCheckout, setShowCheckout] = useState(false); - const { showChat } = useChat(); - const navigate = useNavigate(); - const [timeStarted, setTimeStarted] = useState(""); + const { isLoggedIn } = useAuth(); + const { streamerName } = useParams<{ streamerName: string }>(); + const [streamData, setStreamData] = useState(); + const [viewerCount, setViewerCount] = useState(0); + const { showSideBar } = useSidebar(); + const { isFollowing, checkFollowStatus, followUser, unfollowUser } = useFollow(); + const { showAuthModal, setShowAuthModal } = useAuthModal(); + const [isStripeReady, setIsStripeReady] = useState(false); + const [showCheckout, setShowCheckout] = useState(false); + const { showChat } = useChat(); + const navigate = useNavigate(); + const [timeStarted, setTimeStarted] = useState(""); - useEffect(() => { - // Prevent scrolling when checkout is open - if (showCheckout) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = "unset"; - } - // Cleanup function to ensure overflow is restored when component unmounts - return () => { - document.body.style.overflow = "unset"; - }; - }, [showCheckout]); + useEffect(() => { + // Prevent scrolling when checkout is open + if (showCheckout) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + // Cleanup function to ensure overflow is restored when component unmounts + return () => { + document.body.style.overflow = "unset"; + }; + }, [showCheckout]); - // Increment minutes of stream time every minute - useEffect; + // Increment minutes of stream time every minute + useEffect; - useEffect(() => { - // Fetch stream data for this streamer - fetch(`/api/streams/${streamerId}/data`).then((res) => { - if (!res.ok) { - console.error("Failed to load stream data:", res.statusText); - } - res - .json() - .then((data) => { - const transformedData: StreamType = { - type: "stream", - id: data.stream_id, - username: data.username, - title: data.title, - startTime: data.start_time, - streamCategory: data.category_name, - viewers: data.viewers, - }; - setStreamData(transformedData); + useEffect(() => { + // Fetch stream data for this streamer + fetch(`/api/streams/${streamerId}/data`).then((res) => { + if (!res.ok) { + console.error("Failed to load stream data:", res.statusText); + } + res + .json() + .then((data) => { + const transformedData: StreamType = { + type: "stream", + id: data.stream_id, + username: data.username, + title: data.title, + startTime: data.start_time, + streamCategory: data.category_name, + viewers: data.viewers, + }; + setStreamData(transformedData); - // Check if the logged-in user is following this streamer - if (isLoggedIn) checkFollowStatus(data.username); - }) - .catch((error) => { - console.error("Error fetching stream data:", error); - }); - }); - }, [streamerId]); + // Check if the logged-in user is following this streamer + if (isLoggedIn) checkFollowStatus(data.username); + }) + .catch((error) => { + console.error("Error fetching stream data:", error); + }); + }); + }, [streamerId]); - // Time counter using DD:HH:MM:SS format - useEffect(() => { - if (!streamData?.startTime) return; + // Time counter using DD:HH:MM:SS format + useEffect(() => { + if (!streamData?.startTime) return; - // Initial calculation - const startTime = new Date(streamData.startTime).getTime(); + // Initial calculation + const startTime = new Date(streamData.startTime).getTime(); - const calculateTimeDifference = () => { - // Get the difference in seconds - const diffInSeconds = Math.floor((Date.now() - startTime) / 1000); + const calculateTimeDifference = () => { + // Get the difference in seconds + const diffInSeconds = Math.floor((Date.now() - startTime) / 1000); - // Calculate days, hours, minutes, seconds - const days = Math.floor(diffInSeconds / 86400); - const hours = Math.floor((diffInSeconds % 86400) / 3600); - const minutes = Math.floor((diffInSeconds % 3600) / 60); - const seconds = diffInSeconds % 60; + // Calculate days, hours, minutes, seconds + const days = Math.floor(diffInSeconds / 86400); + const hours = Math.floor((diffInSeconds % 86400) / 3600); + const minutes = Math.floor((diffInSeconds % 3600) / 60); + const seconds = diffInSeconds % 60; - // Format as DD:HH:MM:SS - setTimeStarted( - `${days.toString().padStart(2, "0")}:${hours - .toString() - .padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds - .toString() - .padStart(2, "0")}` - ); - }; + // Format as DD:HH:MM:SS + setTimeStarted( + `${days.toString().padStart(2, "0")}:${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds + .toString() + .padStart(2, "0")}` + ); + }; - // Calculate immediately - calculateTimeDifference(); + // Calculate immediately + calculateTimeDifference(); - // Set up interval to update every second - const intervalId = setInterval(calculateTimeDifference, 1000); + // Set up interval to update every second + const intervalId = setInterval(calculateTimeDifference, 1000); - // Clean up interval on component unmount - return () => clearInterval(intervalId); - }, [streamData?.startTime]); // Re-run if startTime changes + // Clean up interval on component unmount + return () => clearInterval(intervalId); + }, [streamData?.startTime]); // Re-run if startTime changes - // Load Stripe in the background when component mounts - useEffect(() => { - const loadStripe = async () => { - await import("@stripe/stripe-js"); - setIsStripeReady(true); - }; - loadStripe(); - }, []); + // Load Stripe in the background when component mounts + useEffect(() => { + const loadStripe = async () => { + await import("@stripe/stripe-js"); + setIsStripeReady(true); + }; + loadStripe(); + }, []); - return ( - - -
-
- -
+ return ( + + +
+
+ +
- setViewerCount(count)} - /> + setViewerCount(count)} /> - {/* Stream Data */} -
- {/* Streamer Icon */} -
- streamer navigate(`/user/${streamerName}`)} - /> - -
+ {/* Stream Data */} +
+ {/* Streamer Icon */} +
+ { + e.currentTarget.src = "/images/pfps/default.png"; + e.currentTarget.onerror = null; + }} + alt="streamer" + className="w-[3em] h-[3em] rounded-full border-[0.15em] border-purple-500 cursor-pointer" + onClick={() => navigate(`/user/${streamerName}`)} + /> + +
- {/* Stream Title */} -
-

- {streamData ? streamData.title : "Loading..."} -

- - {streamData ? streamData.streamCategory : "Loading..."} - -
+ {/* Stream Title */} +
+

{streamData ? streamData.title : "Loading..."}

+ + {streamData ? streamData.streamCategory : "Loading..."} + +
- {/* Streamer Info */} -
-
- {!isFollowing ? ( - - ) : ( - - )} -
-
+ {/* Streamer Info */} +
+
+ {!isFollowing ? ( + + ) : ( + + )} +
+
- {/* Stream Stats */} -
-
- Viewers Icon - {viewerCount} -
-
+ {/* Stream Stats */} +
+
+ Viewers Icon + {viewerCount} +
+
-
- - {streamData ? timeStarted : "Loading..."} - -
+
+ {streamData ? timeStarted : "Loading..."} +
- {/* Subscribe Button */} -
- -
-
- {showCheckout && ( - Loading checkout...
}> - setShowCheckout(false)} - streamerID={streamerId} - /> - - )} - {/* {showReturn && } */} - {showAuthModal && ( - setShowAuthModal(false)} /> - )} -
-
-
- ); + {/* Subscribe Button */} +
+ +
+
+ {showCheckout && ( + Loading checkout...}> + setShowCheckout(false)} streamerID={streamerId} /> + + )} + {/* {showReturn && } */} + {showAuthModal && setShowAuthModal(false)} />} + +
+
+ ); }; export default VideoPage;