FIX: Navigation from ListItems;

REFACTOR: Format all files;
This commit is contained in:
Chris-1010
2025-02-23 22:57:00 +00:00
parent 5c81f58e66
commit a27ee52de1
34 changed files with 387 additions and 255 deletions

View File

@@ -1,5 +1,5 @@
export const paths = { export const paths = {
pfps:'', pfps: "",
category_thumbnails:'', category_thumbnails: "",
icons:'' icons: "",
}; };

View File

@@ -36,45 +36,64 @@ function App() {
return ( return (
<Brightness> <Brightness>
<AuthContext.Provider <AuthContext.Provider
value={{ isLoggedIn, username, userId, setIsLoggedIn, setUsername, setUserId }} value={{
> isLoggedIn,
<ContentProvider> username,
<SidebarProvider> userId,
<QuickSettingsProvider> setIsLoggedIn,
<BrowserRouter> setUsername,
<Routes> setUserId,
<Route }}
path="/" >
element={ <ContentProvider>
isLoggedIn ? ( <SidebarProvider>
<HomePage variant="personalised" /> <QuickSettingsProvider>
) : ( <BrowserRouter>
<HomePage /> <Routes>
) <Route
} path="/"
/> element={
<Route path="/go-live" element={isLoggedIn ? <StreamDashboardPage /> : <Navigate to="/" replace />} /> isLoggedIn ? (
<Route path="/:streamerName" element={<StreamerRoute />} /> <HomePage variant="personalised" />
<Route path="/user/:username" element={<UserPage />} /> ) : (
<Route <HomePage />
path="/reset_password/:token" )
element={<ResetPasswordPage />} }
></Route> />
<Route <Route
path="/category/:categoryName" path="/go-live"
element={<CategoryPage />} element={
></Route> isLoggedIn ? (
<Route path="/categories" element={<CategoriesPage />}></Route> <StreamDashboardPage />
<Route path="/results" element={<ResultsPage />}></Route> ) : (
<Route path="/404" element={<NotFoundPage />} /> <Navigate to="/" replace />
<Route path="*" element={<Navigate to="/404" replace />} /> )
</Routes> }
</BrowserRouter> />
</QuickSettingsProvider> <Route path="/:streamerName" element={<StreamerRoute />} />
</SidebarProvider> <Route path="/user/:username" element={<UserPage />} />
</ContentProvider> <Route
</AuthContext.Provider> path="/reset_password/:token"
element={<ResetPasswordPage />}
></Route>
<Route
path="/category/:categoryName"
element={<CategoryPage />}
></Route>
<Route
path="/categories"
element={<CategoriesPage />}
></Route>
<Route path="/results" element={<ResultsPage />}></Route>
<Route path="/404" element={<NotFoundPage />} />
<Route path="*" element={<Navigate to="/404" replace />} />
</Routes>
</BrowserRouter>
</QuickSettingsProvider>
</SidebarProvider>
</ContentProvider>
</AuthContext.Provider>
</Brightness> </Brightness>
); );
} }

View File

@@ -1,9 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { ToggleButton } from "../Input/Button"; import { ToggleButton } from "../Input/Button";
import { import { LogIn as LogInIcon, User as UserIcon } from "lucide-react";
LogIn as LogInIcon,
User as UserIcon,
} from "lucide-react";
import LoginForm from "./LoginForm"; import LoginForm from "./LoginForm";
import RegisterForm from "./RegisterForm"; import RegisterForm from "./RegisterForm";
import ForgotPasswordForm from "./ForgotPasswordForm"; import ForgotPasswordForm from "./ForgotPasswordForm";
@@ -93,7 +90,10 @@ const AuthModal: React.FC<AuthModalProps> = ({ onClose }) => {
> >
</button> </button>
<div id="login-methods" className="w-full flex flex-row items-center justify-evenly"> <div
id="login-methods"
className="w-full flex flex-row items-center justify-evenly"
>
{authSwitch()} {authSwitch()}
</div> </div>
</div> </div>
@@ -104,4 +104,4 @@ const AuthModal: React.FC<AuthModalProps> = ({ onClose }) => {
); );
}; };
export default AuthModal; export default AuthModal;

View File

@@ -4,6 +4,7 @@ import Button from "../Input/Button";
interface ForgotPasswordProps { interface ForgotPasswordProps {
email?: string; email?: string;
general?: string;
} }
interface SubmitProps { interface SubmitProps {
@@ -51,7 +52,9 @@ const ForgotPasswordForm: React.FC<SubmitProps> = ({ onSubmit }) => {
if (!response.ok) { if (!response.ok) {
const data = await response.json(); const data = await response.json();
throw new Error(data.message || "An error has occurred while resetting"); throw new Error(
data.message || "An error has occurred while resetting"
);
} else { } else {
confirmPasswordReset(); confirmPasswordReset();
} }
@@ -68,8 +71,10 @@ const ForgotPasswordForm: React.FC<SubmitProps> = ({ onSubmit }) => {
return ( return (
<div className="mb-2"> <div className="mb-2">
<div className="flex flex-col items-center p-[2.5rem]"> <div className="flex flex-col items-center p-[2.5rem]">
<h1 className="text-white text-[1.5em] font-[800] md:text-[1.75em] lg:text-[2em]">Forgot Password</h1> <h1 className="text-white text-[1.5em] font-[800] md:text-[1.75em] lg:text-[2em]">
<div className="mt-10 bg-white/10 backdrop-blur-md p-6 rounded-xl shadow-lg w-full max-w-[10em] min-w-[14em] border border-white/10 sm:max-w-[16em] md:max-w-[18em] lg:max-w-[20em]"> Forgot Password
</h1>
<div className="mt-10 bg-white/10 backdrop-blur-md p-6 rounded-xl shadow-lg w-full max-w-[10em] min-w-[14em] border border-white/10 sm:max-w-[16em] md:max-w-[18em] lg:max-w-[20em]">
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
id="forgot-password-form" id="forgot-password-form"
@@ -93,7 +98,9 @@ const ForgotPasswordForm: React.FC<SubmitProps> = ({ onSubmit }) => {
placeholder="Enter your email" placeholder="Enter your email"
value={email} value={email}
onChange={handleEmailChange} onChange={handleEmailChange}
extraClasses={`w-full mb-[1.5em] p-[0.5rem] ${errors.email ? "border-red-500" : ""}`} extraClasses={`w-full mb-[1.5em] p-[0.5rem] ${
errors.email ? "border-red-500" : ""
}`}
/> />
</div> </div>
<Button type="submit">Send Link</Button> <Button type="submit">Send Link</Button>

View File

@@ -102,9 +102,10 @@ const LoginForm: React.FC<SubmitProps> = ({ onSubmit, onForgotPassword }) => {
return ( return (
<> <>
<div className="flex flex-col items-center p-10"> <div className="flex flex-col items-center p-10">
<h1 className="flex flex-col text-white text-[1.5em] font-[800] md:text-[1.75em] lg:text-[2em]">Login</h1> <h1 className="flex flex-col text-white text-[1.5em] font-[800] md:text-[1.75em] lg:text-[2em]">
Login
</h1>
<div className="mt-10 bg-white/10 backdrop-blur-md p-6 rounded-xl shadow-lg w-full max-w-[10em] min-w-[14em] border border-white/10 sm:max-w-[16em] md:max-w-[18em] lg:max-w-[20em]"> <div className="mt-10 bg-white/10 backdrop-blur-md p-6 rounded-xl shadow-lg w-full max-w-[10em] min-w-[14em] border border-white/10 sm:max-w-[16em] md:max-w-[18em] lg:max-w-[20em]">
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
id="login-form" id="login-form"

View File

@@ -20,7 +20,9 @@ export default function GoogleLogin() {
alt="Google logo" alt="Google logo"
className="w-[2em] h-[2em] mr-2" className="w-[2em] h-[2em] mr-2"
/> />
<span className="flex-grow text-[0.6em] lx:text-[0.75em] 2lg:text-[1em]">Sign in with Google</span> <span className="flex-grow text-[0.6em] lx:text-[0.75em] 2lg:text-[1em]">
Sign in with Google
</span>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -21,7 +21,7 @@ export const Return: React.FC = () => {
const sessionId = urlParams.get("session_id"); const sessionId = urlParams.get("session_id");
if (sessionId) { if (sessionId) {
console.log("1") console.log("1");
fetch(`/api/session-status?session_id=${sessionId}`) fetch(`/api/session-status?session_id=${sessionId}`)
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .then((data) => {

View File

@@ -23,8 +23,7 @@ const Button: React.FC<ButtonProps> = ({
); );
}; };
interface EditButtonProps extends ButtonProps { interface EditButtonProps extends ButtonProps {}
}
export const EditButton: React.FC<EditButtonProps> = ({ export const EditButton: React.FC<EditButtonProps> = ({
children = "", children = "",
@@ -39,7 +38,7 @@ export const EditButton: React.FC<EditButtonProps> = ({
{children} {children}
</button> </button>
); );
}; };
interface ToggleButtonProps extends ButtonProps { interface ToggleButtonProps extends ButtonProps {
toggled?: boolean; toggled?: boolean;

View File

@@ -11,28 +11,26 @@ const Input: React.FC<InputProps> = ({
placeholder = "", placeholder = "",
value = "", value = "",
extraClasses = "", extraClasses = "",
onChange = () => { }, onChange = () => {},
onKeyDown = () => { }, onKeyDown = () => {},
children, children,
...props // all other HTML input props ...props // all other HTML input props
}) => { }) => {
return ( return (
<> <>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<input <input
name={name} name={name}
type={type} type={type}
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
onChange={onChange} onChange={onChange}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
{...props} {...props}
className={`${extraClasses} relative p-2 rounded-[1rem] w-[20vw] focus:w-[22vw] bg-black/40 border border-gray-300 focus:border-purple-500 focus:outline-purple-500 text-center text-white text-xl transition-all`} className={`${extraClasses} relative p-2 rounded-[1rem] w-[20vw] focus:w-[22vw] bg-black/40 border border-gray-300 focus:border-purple-500 focus:outline-purple-500 text-center text-white text-xl transition-all`}
/> />
</div>
</div>
</> </>
); );
}; };

View File

@@ -9,22 +9,27 @@ interface DynamicPageContentProps {
style?: React.CSSProperties; style?: React.CSSProperties;
} }
const DynamicPageContent: React.FC<DynamicPageContentProps> = ({ const DynamicPageContent: React.FC<DynamicPageContentProps> = ({
children, children,
navbarVariant = "default", navbarVariant = "default",
className = "", className = "",
style style,
}) => { }) => {
const { showSideBar } = useSidebar(); const { showSideBar } = useSidebar();
return ( return (
<div className={className} style={style}> <div className={className} style={style}>
<Navbar variant={navbarVariant} /> <Navbar variant={navbarVariant} />
<div id="content" className={`${showSideBar ? "w-[85vw] translate-x-[15vw]" : "w-[100vw]"} transition-all duration-[500ms] ease-in-out`}> <div
id="content"
className={`${
showSideBar ? "w-[85vw] translate-x-[15vw]" : "w-[100vw]"
} transition-all duration-[500ms] ease-in-out`}
>
{children} {children}
</div> </div>
</div> </div>
); );
}; };
export default DynamicPageContent; export default DynamicPageContent;

View File

@@ -65,7 +65,9 @@ const ListItem: React.FC<ListItemProps> = ({
)} )}
</div> </div>
<div className="p-3"> <div className="p-3">
<h3 className="font-semibold text-lg text-center truncate max-w-full">{title}</h3> <h3 className="font-semibold text-lg text-center truncate max-w-full">
{title}
</h3>
{type === "stream" && <p className="font-bold">{username}</p>} {type === "stream" && <p className="font-bold">{username}</p>}
{type === "stream" && ( {type === "stream" && (
<p className="text-sm text-gray-300">{streamCategory}</p> <p className="text-sm text-gray-300">{streamCategory}</p>

View File

@@ -1,11 +1,16 @@
import React, { forwardRef, useImperativeHandle, useRef, useState } from "react";
import { import {
ArrowLeft as ArrowLeftIcon, ArrowLeft as ArrowLeftIcon,
ArrowRight as ArrowRightIcon, ArrowRight as ArrowRightIcon,
} from "lucide-react"; } from "lucide-react";
import React, {
forwardRef,
useImperativeHandle,
useRef,
useState,
} from "react";
import { useNavigate } from "react-router-dom";
import "../../assets/styles/listRow.css"; import "../../assets/styles/listRow.css";
import ListItem, { ListItemProps } from "./ListItem"; import ListItem, { ListItemProps } from "./ListItem";
import { useNavigate } from "react-router-dom";
interface ListRowProps { interface ListRowProps {
variant?: "default" | "search"; variant?: "default" | "search";
@@ -14,7 +19,7 @@ interface ListRowProps {
description?: string; description?: string;
items: ListItemProps[]; items: ListItemProps[];
wrap?: boolean; wrap?: boolean;
onClick: (itemName: string) => void; onItemClick: (itemName: string) => void;
titleClickable?: boolean; titleClickable?: boolean;
extraClasses?: string; extraClasses?: string;
itemExtraClasses?: string; itemExtraClasses?: string;
@@ -22,8 +27,27 @@ interface ListRowProps {
children?: React.ReactNode; children?: React.ReactNode;
} }
const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }, ListRowProps>( const ListRow = forwardRef<
({ variant, type, title = "", description = "", items, wrap, onClick, titleClickable, extraClasses = "", itemExtraClasses = "", amountForScroll, children }, ref) => { { addMoreItems: (newItems: ListItemProps[]) => void },
ListRowProps
>(
(
{
variant = "default",
type,
title = "",
description = "",
items,
onItemClick,
titleClickable = false,
wrap = false,
extraClasses = "",
itemExtraClasses = "",
amountForScroll = 4,
children,
},
ref
) => {
const [currentItems, setCurrentItems] = useState(items); const [currentItems, setCurrentItems] = useState(items);
const slider = useRef<HTMLDivElement>(null); const slider = useRef<HTMLDivElement>(null);
const scrollAmount = window.innerWidth * 0.3; const scrollAmount = window.innerWidth * 0.3;
@@ -79,7 +103,9 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }
> >
<h2 <h2
className={`${ className={`${
titleClickable ? "cursor-pointer hover:underline" : "cursor-default" titleClickable
? "cursor-pointer hover:underline"
: "cursor-default"
} text-2xl font-bold`} } text-2xl font-bold`}
onClick={titleClickable ? () => handleTitleClick(type) : undefined} onClick={titleClickable ? () => handleTitleClick(type) : undefined}
> >
@@ -90,7 +116,7 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }
{/* List Items */} {/* List Items */}
<div className="relative overflow-hidden flex flex-grow items-center z-0"> <div className="relative overflow-hidden flex flex-grow items-center z-0">
{!wrap && currentItems.length > (amountForScroll || 0) && ( {!wrap && currentItems.length > amountForScroll && (
<> <>
<ArrowLeftIcon <ArrowLeftIcon
onClick={slideLeft} onClick={slideLeft}
@@ -126,10 +152,10 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }
onItemClick={() => onItemClick={() =>
(item.type === "stream" || item.type === "user") && (item.type === "stream" || item.type === "user") &&
item.username item.username
? onClick?.(item.username) ? onItemClick?.(item.username)
: onClick?.(item.title) : onItemClick?.(item.title)
} }
extraClasses={`${itemExtraClasses} min-w-[20vw] max-w-[20vw]`} extraClasses={`${itemExtraClasses} w-[20vw]`}
/> />
))} ))}
</div> </div>
@@ -140,4 +166,4 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }
} }
); );
export default ListRow; export default ListRow;

View File

@@ -145,12 +145,12 @@ const Navbar: React.FC<NavbarProps> = ({ variant = "default" }) => {
<SearchBar /> <SearchBar />
{/* Stream Button */} {/* Stream Button */}
{isLoggedIn && !window.location.pathname.includes('go-live') && ( {isLoggedIn && !window.location.pathname.includes("go-live") && (
<Button <Button
extraClasses={`${ extraClasses={`${
variant === "home" ? "absolute top-[2vh] right-[10vw]" : "" variant === "home" ? "absolute top-[2vh] right-[10vw]" : ""
} flex flex-row items-center`} } flex flex-row items-center`}
onClick={() => window.location.href = "/go-live"} onClick={() => (window.location.href = "/go-live")}
> >
<LiveIcon className="h-15 w-15 mr-2" /> <LiveIcon className="h-15 w-15 mr-2" />
Go Live Go Live

View File

@@ -85,22 +85,30 @@ const Sidebar: React.FC<SideBarProps> = ({ extraClasses }) => {
className="font-black text-[1.4rem] hover:underline" className="font-black text-[1.4rem] hover:underline"
onClick={() => navigate(`/user/${username}`)} onClick={() => navigate(`/user/${username}`)}
> >
<div className="text-[var(--sideBar-profile-text)]"> <div className="text-[var(--sideBar-profile-text)]">{username}</div>
{username}
</div>
</button> </button>
</div> </div>
</div> </div>
<div id="following" className="flex flex-col flex-grow justify-evenly gap-4 p-[1rem]"> <div
<div className="bg-[var(--follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300" id="following"
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "var(--follow-shadow)"} className="flex flex-col flex-grow justify-evenly gap-4 p-[1rem]"
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"} >
<div
className="bg-[var(--follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300"
onMouseEnter={(e) =>
(e.currentTarget.style.boxShadow = "var(--follow-shadow)")
}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")}
> >
<h1 className="text-[var(--follow-text)] font-bold text-2xl p-[0.75rem] cursor-default">Following</h1> <h1 className="text-[var(--follow-text)] font-bold text-2xl p-[0.75rem] cursor-default">
Following
</h1>
</div> </div>
<div id="streamers-followed" className="flex-grow"> <div id="streamers-followed" className="flex-grow">
<h2 className="border-b-4 border-t-4 text-2xl cursor-default">Streamers</h2> <h2 className="border-b-4 border-t-4 text-2xl cursor-default">
Streamers
</h2>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
{followedStreamers.map((streamer) => ( {followedStreamers.map((streamer) => (
<li <li
@@ -115,7 +123,9 @@ const Sidebar: React.FC<SideBarProps> = ({ extraClasses }) => {
</div> </div>
<div id="categories-followed" className="flex-grow"> <div id="categories-followed" className="flex-grow">
<h2 className="border-b-4 border-t-4 text-[1.5rem] cursor-default">Categories</h2> <h2 className="border-b-4 border-t-4 text-[1.5rem] cursor-default">
Categories
</h2>
<ul className="mt-2 space-y-2"> <ul className="mt-2 space-y-2">
{followedCategories.map((category) => ( {followedCategories.map((category) => (
<li <li

View File

@@ -2,7 +2,7 @@ import React from "react";
import ThemeSetting from "./ThemeSetting"; import ThemeSetting from "./ThemeSetting";
import { useTheme } from "../../context/ThemeContext"; import { useTheme } from "../../context/ThemeContext";
import { useQuickSettings } from "../../context/QuickSettingsContext"; import { useQuickSettings } from "../../context/QuickSettingsContext";
import Screenshot from "../Functionality/Screenshot" import Screenshot from "../Functionality/Screenshot";
import BrightnessControl from "../Functionality/BrightnessControl"; import BrightnessControl from "../Functionality/BrightnessControl";
const QuickSettings: React.FC = () => { const QuickSettings: React.FC = () => {
@@ -11,19 +11,24 @@ const QuickSettings: React.FC = () => {
return ( return (
<div <div
className={`fixed top-0 right-0 w-[20vw] h-screen p-4 flex flex-col items-center overflow-y-hidden overflow-x-hidden ${showQuickSettings ? "opacity-100 z-[90]" : "opacity-0 z-[-1]" className={`fixed top-0 right-0 w-[20vw] h-screen p-4 flex flex-col items-center overflow-y-hidden overflow-x-hidden ${
} transition-all duration-300 ease-in-out pt-0 bg-[var(--quickBar-bg)] text-[var(--quickBar-text)]`} showQuickSettings ? "opacity-100 z-[90]" : "opacity-0 z-[-1]"
} transition-all duration-300 ease-in-out pt-0 bg-[var(--quickBar-bg)] text-[var(--quickBar-text)]`}
> >
<div className="w-[20vw] p-[1em] flex flex-col items-center bg-[var(--quickBar-title-bg)] text-[var(--quickBar-title)] <div
border-b-[0.25em] border-[var(--quickBar-border)] "> className="w-[20vw] p-[1em] flex flex-col items-center bg-[var(--quickBar-title-bg)] text-[var(--quickBar-title)]
border-b-[0.25em] border-[var(--quickBar-border)] "
>
<h1 className="text-[2rem] font-black">Quick Settings</h1> <h1 className="text-[2rem] font-black">Quick Settings</h1>
</div> </div>
<div id="quick-settings-menu" className="flex flex-col flex-grow my-8 gap-4"> <div
id="quick-settings-menu"
className="flex flex-col flex-grow my-8 gap-4"
>
<ThemeSetting /> <ThemeSetting />
</div> </div>
<Screenshot /> <Screenshot />
<BrightnessControl /> <BrightnessControl />
</div> </div>
); );
}; };

View File

@@ -140,12 +140,13 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
> >
{/* User avatar with image */} {/* User avatar with image */}
<div <div
className={`w-2em h-2em rounded-full overflow-hidden flex-shrink-0 ${msg.chatter_username === username ? "" : "cursor-pointer" className={`w-2em h-2em rounded-full overflow-hidden flex-shrink-0 ${
}`} msg.chatter_username === username ? "" : "cursor-pointer"
}`}
onClick={() => onClick={() =>
msg.chatter_username === username msg.chatter_username === username
? null ? null
: window.location.href = `/user/${msg.chatter_username}` : (window.location.href = `/user/${msg.chatter_username}`)
} }
> >
<img <img
@@ -160,28 +161,33 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
<div className="flex items-center space-x-0.5em"> <div className="flex items-center space-x-0.5em">
{/* Username */} {/* Username */}
<span <span
className={`font-bold text-[1em] ${msg.chatter_username === username className={`font-bold text-[1em] ${
msg.chatter_username === username
? "text-purple-600" ? "text-purple-600"
: "text-green-400 cursor-pointer" : "text-green-400 cursor-pointer"
}`} }`}
onClick={() => onClick={() =>
msg.chatter_username === username msg.chatter_username === username
? null ? null
: window.location.href = `/user/${msg.chatter_username}` : (window.location.href = `/user/${msg.chatter_username}`)
} }
> >
{msg.chatter_username} {msg.chatter_username}
</span> </span>
</div> </div>
{/* Message content */} {/* Message content */}
<div className="message w-full text-[0.9em] mt-0.5em flex flex-col overflow-hidden" > <div className="message w-full text-[0.9em] mt-0.5em flex flex-col overflow-hidden">
{msg.message} {msg.message}
</div> </div>
</div> </div>
{/* Time sent */} {/* Time sent */}
<div className="text-gray-500 text-[0.8em] absolute top-0 right-0 p-2"> <div className="text-gray-500 text-[0.8em] absolute top-0 right-0 p-2">
{new Date(msg.time_sent).toLocaleTimeString('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit' })} {new Date(msg.time_sent).toLocaleTimeString("en-GB", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
})}
</div> </div>
</div> </div>
))} ))}

View File

@@ -44,12 +44,12 @@ const StreamerRoute: React.FC = () => {
if (isLive) { if (isLive) {
return <VideoPage streamerId={streamId} />; return <VideoPage streamerId={streamId} />;
} }
if (streamerName) { if (streamerName) {
navigate(`/user/${streamerName}`); navigate(`/user/${streamerName}`);
return null; return null;
} }
return <div>Streamer not found</div>; return <div>Streamer not found</div>;
}; };

View File

@@ -1,5 +1,3 @@
import React from "react";
interface ThumbnailProps { interface ThumbnailProps {
path: string; path: string;
alt?: string; alt?: string;

View File

@@ -32,7 +32,6 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
const setupPlayer = async () => { const setupPlayer = async () => {
const streamKey = await fetchStreamKey(); const streamKey = await fetchStreamKey();
const streamUrl = `/stream/${streamKey}/index.m3u8`; const streamUrl = `/stream/${streamKey}/index.m3u8`;
console.log("Player created with src:", streamUrl);
if (!playerRef.current) { if (!playerRef.current) {
const videoElement = document.createElement("video"); const videoElement = document.createElement("video");

View File

@@ -4,15 +4,19 @@ import { useBrightness } from "../../context/BrightnessContext";
const BrightnessControl: React.FC = () => { const BrightnessControl: React.FC = () => {
const { brightness, setBrightness } = useBrightness(); const { brightness, setBrightness } = useBrightness();
const handleBrightnessChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleBrightnessChange = (
{/* Set brightness based on the value. Calls BrightnessContext too */} event: React.ChangeEvent<HTMLInputElement>
) => {
{
/* Set brightness based on the value. Calls BrightnessContext too */
}
setBrightness(Number(event.target.value)); setBrightness(Number(event.target.value));
}; };
return ( return (
<div className="flex flex-col items-center p-4"> <div className="flex flex-col items-center p-4">
<h2 className="text-lg font-semibold mb-2">Brightness Control</h2> <h2 className="text-lg font-semibold mb-2">Brightness Control</h2>
{/* Changes based on the range of input */} {/* Changes based on the range of input */}
<input <input
type="range" type="range"
min="0" min="0"

View File

@@ -5,9 +5,13 @@ interface BrightnessContextType {
setBrightness: (value: number) => void; setBrightness: (value: number) => void;
} }
const BrightnessContext = createContext<BrightnessContextType | undefined>(undefined); const BrightnessContext = createContext<BrightnessContextType | undefined>(
undefined
);
export const Brightness: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const Brightness: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [brightness, setBrightness] = useState<number>(100); const [brightness, setBrightness] = useState<number>(100);
useEffect(() => { useEffect(() => {

View File

@@ -45,40 +45,45 @@ export function ContentProvider({ children }: { children: React.ReactNode }) {
useEffect(() => { useEffect(() => {
// Fetch streams // Fetch streams
const streamsUrl = isLoggedIn const streamsUrl = isLoggedIn
? "/api/streams/recommended" ? "/api/streams/recommended"
: "/api/streams/popular/4"; : "/api/streams/popular/4";
fetch(streamsUrl) fetch(streamsUrl)
.then((response) => response.json()) .then((response) => response.json())
.then((data: any[]) => { .then((data: any[]) => {
const processedStreams: StreamItem[] = data.map(stream => ({ const processedStreams: StreamItem[] = data.map((stream) => ({
type: "stream", type: "stream",
id: stream.user_id, id: stream.user_id,
title: stream.title, title: stream.title,
streamer: stream.username, streamer: stream.username,
streamCategory: stream.category_name, streamCategory: stream.category_name,
viewers: stream.num_viewers, viewers: stream.num_viewers,
thumbnail: stream.thumbnail || thumbnail:
`/images/category_thumbnails/${stream.category_name.toLowerCase().replace(/ /g, "_")}.webp` stream.thumbnail ||
`/images/category_thumbnails/${stream.category_name
.toLowerCase()
.replace(/ /g, "_")}.webp`,
})); }));
setStreams(processedStreams); setStreams(processedStreams);
}); });
// Fetch categories // Fetch categories
const categoriesUrl = isLoggedIn const categoriesUrl = isLoggedIn
? "/api/categories/recommended" ? "/api/categories/recommended"
: "/api/categories/popular/4"; : "/api/categories/popular/4";
fetch(categoriesUrl) fetch(categoriesUrl)
.then((response) => response.json()) .then((response) => response.json())
.then((data: any[]) => { .then((data: any[]) => {
const processedCategories: CategoryItem[] = data.map(category => ({ const processedCategories: CategoryItem[] = data.map((category) => ({
type: "category", type: "category",
id: category.category_id, id: category.category_id,
title: category.category_name, title: category.category_name,
viewers: category.num_viewers, viewers: category.num_viewers,
thumbnail: `/images/category_thumbnails/${category.category_name.toLowerCase().replace(/ /g, "_")}.webp`, thumbnail: `/images/category_thumbnails/${category.category_name
.toLowerCase()
.replace(/ /g, "_")}.webp`,
})); }));
setCategories(processedCategories); setCategories(processedCategories);
}); });
@@ -114,7 +119,10 @@ export function useCategories() {
if (!context) { if (!context) {
throw new Error("useCategories must be used within a ContentProvider"); throw new Error("useCategories must be used within a ContentProvider");
} }
return { categories: context.categories, setCategories: context.setCategories }; return {
categories: context.categories,
setCategories: context.setCategories,
};
} }
export function useUsers() { export function useUsers() {

View File

@@ -23,4 +23,4 @@ export function useSidebar() {
throw new Error("useSidebar must be used within a SidebarProvider"); throw new Error("useSidebar must be used within a SidebarProvider");
} }
return context; return context;
} }

View File

@@ -1,9 +1,15 @@
import { createContext, useContext, useState, useEffect, ReactNode } from "react"; import {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from "react";
// Defines the Theme (Colour Theme) that would be shared/used // Defines the Theme (Colour Theme) that would be shared/used
interface ThemeContextType { interface ThemeContextType {
theme: string; theme: string;
setTheme: (theme: string) => void; setTheme: (theme: string) => void;
} }
// Store theme and provide access to setTheme function // Store theme and provide access to setTheme function
@@ -11,44 +17,45 @@ interface ThemeContextType {
const ThemeContext = createContext<ThemeContextType | undefined>(undefined); const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider = ({ children }: { children: ReactNode }) => { export const ThemeProvider = ({ children }: { children: ReactNode }) => {
// Set default theme to dark // Set default theme to dark
const [theme, setTheme] = useState<string>(() => { const [theme, setTheme] = useState<string>(() => {
// If exist on user cache, use that instead // If exist on user cache, use that instead
return localStorage.getItem("user-theme") || "dark"; return localStorage.getItem("user-theme") || "dark";
}); });
useEffect(() => { useEffect(() => {
// Store current theme set by user // Store current theme set by user
localStorage.setItem("user-theme", theme); localStorage.setItem("user-theme", theme);
// Update the theme // Update the theme
document.body.setAttribute("data-theme", theme); document.body.setAttribute("data-theme", theme);
}, [theme]); }, [theme]);
return ( return (
// Sets the selected theme to child component // Sets the selected theme to child component
<ThemeContext.Provider value={{ theme, setTheme }}> <ThemeContext.Provider value={{ theme, setTheme }}>
{children} {children}
</ThemeContext.Provider> </ThemeContext.Provider>
); );
}; };
// Custom Hook which allows any component to access theme & setTheme with "useTheme()" // Custom Hook which allows any component to access theme & setTheme with "useTheme()"
export const useTheme = () => { export const useTheme = () => {
const context = useContext(ThemeContext); //Retrieves current value of context const context = useContext(ThemeContext); //Retrieves current value of context
if (!context) { if (!context) {
throw new Error("useTheme must be used within a ThemeProvider"); //If called outside of ThemeContext.tsx, errorHandle throw new Error("useTheme must be used within a ThemeProvider"); //If called outside of ThemeContext.tsx, errorHandle
} }
return context; return context;
}; };
{
{/** /**
createContext: Allow components to share data without directly passing props through multiple levels createContext: Allow components to share data without directly passing props through multiple levels
useContext: Allows a component to access the current value of a context ("Hook") useContext: Allows a component to access the current value of a context ("Hook")
useState: Manages state of a component ("Hook") useState: Manages state of a component ("Hook")
ReactNode: Allows to take in HTML / React / Arrays of Component ReactNode: Allows to take in HTML / React / Arrays of Component
*/} */
}

View File

@@ -15,7 +15,7 @@ export function useCategoryFollow() {
} }
}; };
const followCategory = async (categoryName: number) => { const followCategory = async (categoryName: string) => {
if (!isLoggedIn) { if (!isLoggedIn) {
return; return;
} }
@@ -34,7 +34,7 @@ export function useCategoryFollow() {
} }
}; };
const unfollowCategory = async (categoryName: number) => { const unfollowCategory = async (categoryName: string) => {
if (!isLoggedIn) { if (!isLoggedIn) {
return; return;
} }

View File

@@ -5,6 +5,6 @@ import App from "./App.tsx";
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<ThemeProvider> <ThemeProvider>
<App /> <App />
</ThemeProvider> </ThemeProvider>
); );

View File

@@ -17,29 +17,31 @@ const AllCategoriesPage: React.FC = () => {
const [categoryOffset, setCategoryOffset] = useState(0); const [categoryOffset, setCategoryOffset] = useState(0);
const [noCategories, setNoCategories] = useState(12); const [noCategories, setNoCategories] = useState(12);
const [hasMoreData, setHasMoreData] = useState(true); const [hasMoreData, setHasMoreData] = useState(true);
const listRowRef = useRef<any>(null); const listRowRef = useRef<any>(null);
const isLoading = useRef(false); const isLoading = useRef(false);
const fetchCategories = async () => { const fetchCategories = async () => {
// If already loading, skip this fetch // If already loading, skip this fetch
if (isLoading.current) return; if (isLoading.current) return;
isLoading.current = true; isLoading.current = true;
try { try {
const response = await fetch(`/api/categories/popular/${noCategories}/${categoryOffset}`); const response = await fetch(
`/api/categories/popular/${noCategories}/${categoryOffset}`
);
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to fetch categories"); throw new Error("Failed to fetch categories");
} }
const data = await response.json(); const data = await response.json();
if (data.length === 0) { if (data.length === 0) {
setHasMoreData(false); setHasMoreData(false);
return []; return [];
} }
setCategoryOffset(prev => prev + data.length); setCategoryOffset((prev) => prev + data.length);
const processedCategories = data.map((category: any) => ({ const processedCategories = data.map((category: any) => ({
type: "category" as const, type: "category" as const,
@@ -51,7 +53,7 @@ const AllCategoriesPage: React.FC = () => {
.replace(/ /g, "_")}.webp`, .replace(/ /g, "_")}.webp`,
})); }));
setCategories(prev => [...prev, ...processedCategories]); setCategories((prev) => [...prev, ...processedCategories]);
return processedCategories; return processedCategories;
} catch (error) { } catch (error) {
console.error("Error fetching categories:", error); console.error("Error fetching categories:", error);
@@ -99,7 +101,7 @@ const AllCategoriesPage: React.FC = () => {
type="category" type="category"
title="All Categories" title="All Categories"
items={categories} items={categories}
onClick={handleCategoryClick} onItemClick={handleCategoryClick}
extraClasses="bg-[var(--recommend)] text-center" extraClasses="bg-[var(--recommend)] text-center"
wrap={true} wrap={true}
/> />
@@ -107,4 +109,4 @@ const AllCategoriesPage: React.FC = () => {
); );
}; };
export default AllCategoriesPage; export default AllCategoriesPage;

View File

@@ -6,16 +6,7 @@ import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll";
import Button from "../components/Input/Button"; import Button from "../components/Input/Button";
import { useAuth } from "../context/AuthContext"; import { useAuth } from "../context/AuthContext";
import { useCategoryFollow } from "../hooks/useCategoryFollow"; import { useCategoryFollow } from "../hooks/useCategoryFollow";
import { ListItemProps as StreamData } from "../components/Layout/ListItem";
interface StreamData {
type: "stream";
id: number;
title: string;
streamer: string;
streamCategory: string;
viewers: number;
thumbnail?: string;
}
const CategoryPage: React.FC = () => { const CategoryPage: React.FC = () => {
const { categoryName } = useParams<{ categoryName: string }>(); const { categoryName } = useParams<{ categoryName: string }>();
@@ -26,10 +17,15 @@ const CategoryPage: React.FC = () => {
const [noStreams, setNoStreams] = useState(12); const [noStreams, setNoStreams] = useState(12);
const [hasMoreData, setHasMoreData] = useState(true); const [hasMoreData, setHasMoreData] = useState(true);
const { isLoggedIn } = useAuth(); const { isLoggedIn } = useAuth();
const { isCategoryFollowing, checkCategoryFollowStatus, followCategory, unfollowCategory } = useCategoryFollow() const {
isCategoryFollowing,
checkCategoryFollowStatus,
followCategory,
unfollowCategory,
} = useCategoryFollow();
useEffect(() => { useEffect(() => {
checkCategoryFollowStatus(categoryName); if (categoryName) checkCategoryFollowStatus(categoryName);
}, [categoryName]); }, [categoryName]);
const fetchCategoryStreams = async () => { const fetchCategoryStreams = async () => {
@@ -38,7 +34,9 @@ const CategoryPage: React.FC = () => {
isLoading.current = true; isLoading.current = true;
try { try {
const response = await fetch(`/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}`); const response = await fetch(
`/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}`
);
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to fetch category streams"); throw new Error("Failed to fetch category streams");
} }
@@ -49,13 +47,13 @@ const CategoryPage: React.FC = () => {
return []; return [];
} }
setStreamOffset(prev => prev + data.length); setStreamOffset((prev) => prev + data.length);
const processedStreams = data.map((stream: any) => ({ const processedStreams = data.map((stream: any) => ({
type: "stream", type: "stream",
id: stream.user_id, id: stream.user_id,
title: stream.title, title: stream.title,
streamer: stream.username, username: stream.username,
streamCategory: categoryName, streamCategory: categoryName,
viewers: stream.num_viewers, viewers: stream.num_viewers,
thumbnail: thumbnail:
@@ -66,8 +64,8 @@ const CategoryPage: React.FC = () => {
.replace(/ /g, "_")}.webp`), .replace(/ /g, "_")}.webp`),
})); }));
setStreams(prev => [...prev, ...processedStreams]); setStreams((prev) => [...prev, ...processedStreams]);
return processedStreams return processedStreams;
} catch (error) { } catch (error) {
console.error("Error fetching category streams:", error); console.error("Error fetching category streams:", error);
} finally { } finally {
@@ -90,7 +88,6 @@ const CategoryPage: React.FC = () => {
fetchContentOnScroll(logOnScroll, hasMoreData); fetchContentOnScroll(logOnScroll, hasMoreData);
const handleStreamClick = (streamerName: string) => { const handleStreamClick = (streamerName: string) => {
window.location.href = `/${streamerName}`; window.location.href = `/${streamerName}`;
}; };
@@ -115,14 +112,18 @@ const CategoryPage: React.FC = () => {
description={`Live streams in the ${categoryName} category`} description={`Live streams in the ${categoryName} category`}
items={streams} items={streams}
wrap={true} wrap={true}
onClick={handleStreamClick} onItemClick={handleStreamClick}
extraClasses="bg-[var(--recommend)]" extraClasses="bg-[var(--recommend)]"
> >
{isLoggedIn && ( {isLoggedIn && (
<Button <Button
extraClasses="absolute right-10" extraClasses="absolute right-10"
onClick={() => { onClick={() => {
isCategoryFollowing ? unfollowCategory(categoryName) : followCategory(categoryName) if (categoryName) {
isCategoryFollowing
? unfollowCategory(categoryName)
: followCategory(categoryName);
}
}} }}
> >
{isCategoryFollowing ? "Unfollow" : "Follow"} {isCategoryFollowing ? "Unfollow" : "Follow"}

View File

@@ -1,4 +1,4 @@
import React, { useRef, useEffect } from "react"; import React from "react";
import ListRow from "../components/Layout/ListRow"; import ListRow from "../components/Layout/ListRow";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useStreams, useCategories } from "../context/ContentContext"; import { useStreams, useCategories } from "../context/ContentContext";
@@ -45,7 +45,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
} }
items={streams} items={streams}
wrap={false} wrap={false}
onClick={handleStreamClick} onItemClick={handleStreamClick}
extraClasses="bg-[var(--liveNow)]" extraClasses="bg-[var(--liveNow)]"
/> />
@@ -64,7 +64,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
} }
items={categories} items={categories}
wrap={false} wrap={false}
onClick={handleCategoryClick} onItemClick={handleCategoryClick}
titleClickable={true} titleClickable={true}
extraClasses="bg-[var(--recommend)]" extraClasses="bg-[var(--recommend)]"
> >
@@ -79,4 +79,4 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
); );
}; };
export default HomePage; export default HomePage;

View File

@@ -4,7 +4,9 @@ import Button from "../components/Input/Button";
import ChromeDinoGame from "react-chrome-dino"; import ChromeDinoGame from "react-chrome-dino";
const NotFoundPage: React.FC = () => { const NotFoundPage: React.FC = () => {
const [stars, setStars] = useState<{ x: number; y: number, xChange: number, yChange: number }[]>([]); const [stars, setStars] = useState<
{ x: number; y: number; xChange: number; yChange: number }[]
>([]);
const starSize = 30; const starSize = 30;
const [score, setScore] = useState(0); const [score, setScore] = useState(0);
@@ -16,9 +18,15 @@ const NotFoundPage: React.FC = () => {
const loop = setInterval(() => { const loop = setInterval(() => {
if (Math.random() < 0.1) { if (Math.random() < 0.1) {
const newStar = { const newStar = {
x: score > 20000 ? (window.innerWidth + starSize) : Math.random() * (window.innerWidth - starSize), x:
y: score > 20000 ? Math.random() * (window.innerHeight - starSize) : -starSize, score > 20000
xChange: score * .001, ? window.innerWidth + starSize
: Math.random() * (window.innerWidth - starSize),
y:
score > 20000
? Math.random() * (window.innerHeight - starSize)
: -starSize,
xChange: score * 0.001,
yChange: 5, yChange: 5,
}; };
setStars((prev) => [...prev, newStar]); setStars((prev) => [...prev, newStar]);
@@ -39,7 +47,7 @@ const NotFoundPage: React.FC = () => {
return newStars.map((star) => ({ return newStars.map((star) => ({
x: star.x - star.xChange, x: star.x - star.xChange,
y: star.y + star.yChange, y: star.y + star.yChange,
xChange: score * .001, xChange: score * 0.001,
yChange: star.yChange, yChange: star.yChange,
})); }));
}); });
@@ -68,7 +76,15 @@ const NotFoundPage: React.FC = () => {
}, []); }, []);
return ( return (
<div className={`h-screen w-screen ${score > 25000 ? "bg-black" : score > 10000 ? "bg-[#0f0024]" : "bg-slate-900"} text-white overflow-hidden relative transition-colors duration-[5s]`}> <div
className={`h-screen w-screen ${
score > 25000
? "bg-black"
: score > 10000
? "bg-[#0f0024]"
: "bg-slate-900"
} text-white overflow-hidden relative transition-colors duration-[5s]`}
>
<div> <div>
{stars.map((star, index) => ( {stars.map((star, index) => (
<div <div
@@ -81,7 +97,11 @@ const NotFoundPage: React.FC = () => {
))} ))}
</div> </div>
<div className="absolute flex justify-center items-center h-full z-0 inset-0 bg-[radial-gradient(rgba(255,255,255,0.5)_1px,transparent_1px)] bg-[length:50px_50px]"> <div className="absolute flex justify-center items-center h-full z-0 inset-0 bg-[radial-gradient(rgba(255,255,255,0.5)_1px,transparent_1px)] bg-[length:50px_50px]">
<div className={`${score > 30000 && "drop-shadow-[0_0_5px_rgb(220,20,60)]" } w-full text-center animate-floating transition-all duration-[5s]`}> <div
className={`${
score > 30000 && "drop-shadow-[0_0_5px_rgb(220,20,60)]"
} w-full text-center animate-floating transition-all duration-[5s]`}
>
<h1 className="text-6xl font-bold mb-4">404</h1> <h1 className="text-6xl font-bold mb-4">404</h1>
<p className="text-2xl mb-8">Page Not Found</p> <p className="text-2xl mb-8">Page Not Found</p>
<ChromeDinoGame /> <ChromeDinoGame />

View File

@@ -3,28 +3,29 @@ import PasswordResetForm from "../components/Auth/PasswordResetForm";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
const ResetPasswordPage: React.FC = () => { const ResetPasswordPage: React.FC = () => {
const { token } = useParams<{ token: string }>(); const { token } = useParams<{ token: string }>();
const handlePasswordReset = (success: boolean) => { const handlePasswordReset = (success: boolean) => {
if (success) { if (success) {
alert("Password reset successful!"); alert("Password reset successful!");
window.location.href = "/"; window.location.href = "/";
} } else {
else { alert("Password reset failed.");
alert("Password reset failed.");
}
};
if (!token) {
return <p className="text-red-500 text-center mt-4">Invalid or missing token.</p>;
} }
};
if (!token) {
return ( return (
<div className="flex flex-col items-center justify-center h-screen"> <p className="text-red-500 text-center mt-4">Invalid or missing token.</p>
<h1 className="text-2xl font-bold mb-4">Forgot Password</h1>
<PasswordResetForm onSubmit={handlePasswordReset} token={token} />
</div>
); );
}
return (
<div className="flex flex-col items-center justify-center h-screen">
<h1 className="text-2xl font-bold mb-4">Forgot Password</h1>
<PasswordResetForm onSubmit={handlePasswordReset} token={token} />
</div>
);
}; };
export default ResetPasswordPage; export default ResetPasswordPage;

View File

@@ -70,7 +70,7 @@ const ResultsPage: React.FC = ({}) => {
thumbnail: stream.thumbnail_url, thumbnail: stream.thumbnail_url,
}))} }))}
title="Streams" title="Streams"
onClick={(streamer_name: string) => onItemClick={(streamer_name: string) =>
(window.location.href = `/${streamer_name}`) (window.location.href = `/${streamer_name}`)
} }
itemExtraClasses="min-w-[calc(12vw+12vh/2)]" itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
@@ -92,7 +92,7 @@ const ResultsPage: React.FC = ({}) => {
.replace(/ /g, "_")}.webp`, .replace(/ /g, "_")}.webp`,
}))} }))}
title="Categories" title="Categories"
onClick={(category_name: string) => onItemClick={(category_name: string) =>
navigate(`/category/${category_name}`) navigate(`/category/${category_name}`)
} }
titleClickable={true} titleClickable={true}
@@ -114,7 +114,7 @@ const ResultsPage: React.FC = ({}) => {
thumbnail: user.profile_picture, thumbnail: user.profile_picture,
}))} }))}
title="Users" title="Users"
onClick={(username: string) => onItemClick={(username: string) =>
(window.location.href = `/user/${username}`) (window.location.href = `/user/${username}`)
} }
amountForScroll={3} amountForScroll={3}

View File

@@ -235,7 +235,7 @@ const StreamDashboardPage: React.FC = () => {
if (thumbnail) { if (thumbnail) {
formData.append("thumbnail", thumbnail); formData.append("thumbnail", thumbnail);
} }
try { try {
const response = await fetch("/api/update_stream", { const response = await fetch("/api/update_stream", {
method: "POST", method: "POST",
@@ -461,7 +461,7 @@ const StreamDashboardPage: React.FC = () => {
type="stream" type="stream"
id={1} id={1}
title={streamData.title || "Stream Title"} title={streamData.title || "Stream Title"}
streamer={username || ""} username={username || ""}
streamCategory={streamData.category_name || "Category"} streamCategory={streamData.category_name || "Category"}
viewers={streamData.viewer_count} viewers={streamData.viewer_count}
thumbnail={thumbnailPreview.url || ""} thumbnail={thumbnailPreview.url || ""}

View File

@@ -116,7 +116,6 @@ const UserPage: React.FC = () => {
} text-white flex flex-col`} } text-white flex flex-col`}
> >
<div className="flex justify-evenly justify-self-center items-center h-full px-4 py-8 max-w-[80vw] w-full"> <div className="flex justify-evenly justify-self-center items-center h-full px-4 py-8 max-w-[80vw] w-full">
<div className="grid grid-cols-4 grid-rows-[0.1fr_4fr] w-full gap-8"> <div className="grid grid-cols-4 grid-rows-[0.1fr_4fr] w-full gap-8">
{/* Profile Section - TOP */} {/* Profile Section - TOP */}
@@ -129,9 +128,9 @@ const UserPage: React.FC = () => {
{/* Border Overlay (Always on Top) */} {/* Border Overlay (Always on Top) */}
<div className="absolute left-[0px] inset-0 border-[5px] border-[var(--user-borderBg)] rounded-[20px] z-20"></div> <div className="absolute left-[0px] inset-0 border-[5px] border-[var(--user-borderBg)] rounded-[20px] z-20"></div>
{/* Background Box */} {/* Background Box */}
<div className="absolute flex top-0 left-[0.55px] w-[99.9%] h-[5vh] min-h-[1em] max-h-[10em] rounded-t-[25.5px] <div
className="absolute flex top-0 left-[0.55px] w-[99.9%] h-[5vh] min-h-[1em] max-h-[10em] rounded-t-[25.5px]
bg-[var(--user-box)] z-10 flex-shrink justify-center" bg-[var(--user-box)] z-10 flex-shrink justify-center"
style={{ boxShadow: "var(--user-box-shadow)" }} style={{ boxShadow: "var(--user-box-shadow)" }}
> >
@@ -197,7 +196,9 @@ const UserPage: React.FC = () => {
className="col-span-1 bg-[var(--user-sideBox)] rounded-lg p-6 grid grid-rows-[auto_1fr] text-center items-center justify-center" className="col-span-1 bg-[var(--user-sideBox)] rounded-lg p-6 grid grid-rows-[auto_1fr] text-center items-center justify-center"
> >
{/* User Type (e.g., "USER") */} {/* User Type (e.g., "USER") */}
<small className="text-green-400">{userPageVariant.toUpperCase()}</small> <small className="text-green-400">
{userPageVariant.toUpperCase()}
</small>
<div className="mt-6 text-center"> <div className="mt-6 text-center">
<h2 className="text-xl font-semibold mb-3"> <h2 className="text-xl font-semibold mb-3">
@@ -258,7 +259,6 @@ const UserPage: React.FC = () => {
</div> </div>
</> </>
)} )}
</div> </div>
<div <div
@@ -269,31 +269,39 @@ const UserPage: React.FC = () => {
<div <div
className="bg-[var(--user-follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300 className="bg-[var(--user-follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300
flex items-center justify-center w-full p-4 content-start" flex items-center justify-center w-full p-4 content-start"
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "var(--follow-shadow)"} onMouseEnter={(e) =>
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"} (e.currentTarget.style.boxShadow = "var(--follow-shadow)")
}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")}
> >
<li className="text-[var(--follow-text)] whitespace-pre-wrap">Following</li> <li className="text-[var(--follow-text)] whitespace-pre-wrap">
Following
</li>
</div> </div>
<div <div
className="bg-[var(--user-follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300 className="bg-[var(--user-follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300
flex items-center justify-center w-full p-4 content-start" flex items-center justify-center w-full p-4 content-start"
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "var(--follow-shadow)"} onMouseEnter={(e) =>
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"} (e.currentTarget.style.boxShadow = "var(--follow-shadow)")
}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")}
> >
<li className="text-[var(--follow-text)] whitespace-pre-wrap">Streamers</li> <li className="text-[var(--follow-text)] whitespace-pre-wrap">
Streamers
</li>
</div> </div>
<div <div
className="bg-[var(--user-follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300 className="bg-[var(--user-follow-bg)] rounded-[1em] hover:scale-105 transition-all ease-in-out duration-300
flex items-center justify-center w-full p-4 content-start" flex items-center justify-center w-full p-4 content-start"
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "var(--follow-shadow)"} onMouseEnter={(e) =>
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"} (e.currentTarget.style.boxShadow = "var(--follow-shadow)")
}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")}
> >
<li className="text-[var(--follow-text)] whitespace-pre-wrap">Category</li> <li className="text-[var(--follow-text)] whitespace-pre-wrap">
Category
</li>
</div> </div>
</div> </div>
</div> </div>
</div> </div>