FIX: Navigation from ListItems;
REFACTOR: Format all files;
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
export const paths = {
|
||||
pfps:'',
|
||||
category_thumbnails:'',
|
||||
icons:''
|
||||
};
|
||||
pfps: "",
|
||||
category_thumbnails: "",
|
||||
icons: "",
|
||||
};
|
||||
|
||||
@@ -36,45 +36,64 @@ function App() {
|
||||
|
||||
return (
|
||||
<Brightness>
|
||||
<AuthContext.Provider
|
||||
value={{ isLoggedIn, username, userId, setIsLoggedIn, setUsername, setUserId }}
|
||||
>
|
||||
<ContentProvider>
|
||||
<SidebarProvider>
|
||||
<QuickSettingsProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
isLoggedIn ? (
|
||||
<HomePage variant="personalised" />
|
||||
) : (
|
||||
<HomePage />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route path="/go-live" element={isLoggedIn ? <StreamDashboardPage /> : <Navigate to="/" replace />} />
|
||||
<Route path="/:streamerName" element={<StreamerRoute />} />
|
||||
<Route path="/user/:username" element={<UserPage />} />
|
||||
<Route
|
||||
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>
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
isLoggedIn,
|
||||
username,
|
||||
userId,
|
||||
setIsLoggedIn,
|
||||
setUsername,
|
||||
setUserId,
|
||||
}}
|
||||
>
|
||||
<ContentProvider>
|
||||
<SidebarProvider>
|
||||
<QuickSettingsProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
isLoggedIn ? (
|
||||
<HomePage variant="personalised" />
|
||||
) : (
|
||||
<HomePage />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/go-live"
|
||||
element={
|
||||
isLoggedIn ? (
|
||||
<StreamDashboardPage />
|
||||
) : (
|
||||
<Navigate to="/" replace />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route path="/:streamerName" element={<StreamerRoute />} />
|
||||
<Route path="/user/:username" element={<UserPage />} />
|
||||
<Route
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { ToggleButton } from "../Input/Button";
|
||||
import {
|
||||
LogIn as LogInIcon,
|
||||
User as UserIcon,
|
||||
} from "lucide-react";
|
||||
import { LogIn as LogInIcon, User as UserIcon } from "lucide-react";
|
||||
import LoginForm from "./LoginForm";
|
||||
import RegisterForm from "./RegisterForm";
|
||||
import ForgotPasswordForm from "./ForgotPasswordForm";
|
||||
@@ -93,7 +90,10 @@ const AuthModal: React.FC<AuthModalProps> = ({ onClose }) => {
|
||||
>
|
||||
✕
|
||||
</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()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Button from "../Input/Button";
|
||||
|
||||
interface ForgotPasswordProps {
|
||||
email?: string;
|
||||
general?: string;
|
||||
}
|
||||
|
||||
interface SubmitProps {
|
||||
@@ -51,7 +52,9 @@ const ForgotPasswordForm: React.FC<SubmitProps> = ({ onSubmit }) => {
|
||||
|
||||
if (!response.ok) {
|
||||
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 {
|
||||
confirmPasswordReset();
|
||||
}
|
||||
@@ -68,8 +71,10 @@ const ForgotPasswordForm: React.FC<SubmitProps> = ({ onSubmit }) => {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<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>
|
||||
<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]">
|
||||
<h1 className="text-white text-[1.5em] font-[800] md:text-[1.75em] lg:text-[2em]">
|
||||
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
|
||||
onSubmit={handleSubmit}
|
||||
id="forgot-password-form"
|
||||
@@ -93,7 +98,9 @@ const ForgotPasswordForm: React.FC<SubmitProps> = ({ onSubmit }) => {
|
||||
placeholder="Enter your email"
|
||||
value={email}
|
||||
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>
|
||||
<Button type="submit">Send Link</Button>
|
||||
|
||||
@@ -102,9 +102,10 @@ const LoginForm: React.FC<SubmitProps> = ({ onSubmit, onForgotPassword }) => {
|
||||
return (
|
||||
<>
|
||||
<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]">
|
||||
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
id="login-form"
|
||||
|
||||
@@ -20,7 +20,9 @@ export default function GoogleLogin() {
|
||||
alt="Google logo"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const Return: React.FC = () => {
|
||||
const sessionId = urlParams.get("session_id");
|
||||
|
||||
if (sessionId) {
|
||||
console.log("1")
|
||||
console.log("1");
|
||||
fetch(`/api/session-status?session_id=${sessionId}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
|
||||
@@ -23,8 +23,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface EditButtonProps extends ButtonProps {
|
||||
}
|
||||
interface EditButtonProps extends ButtonProps {}
|
||||
|
||||
export const EditButton: React.FC<EditButtonProps> = ({
|
||||
children = "",
|
||||
|
||||
@@ -11,28 +11,26 @@ const Input: React.FC<InputProps> = ({
|
||||
placeholder = "",
|
||||
value = "",
|
||||
extraClasses = "",
|
||||
onChange = () => { },
|
||||
onKeyDown = () => { },
|
||||
onChange = () => {},
|
||||
onKeyDown = () => {},
|
||||
children,
|
||||
...props // all other HTML input props
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-center">
|
||||
<input
|
||||
name={name}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
{...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`}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<input
|
||||
name={name}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
{...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`}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,14 +13,19 @@ const DynamicPageContent: React.FC<DynamicPageContentProps> = ({
|
||||
children,
|
||||
navbarVariant = "default",
|
||||
className = "",
|
||||
style
|
||||
style,
|
||||
}) => {
|
||||
const { showSideBar } = useSidebar();
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<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}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,9 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||
)}
|
||||
</div>
|
||||
<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="text-sm text-gray-300">{streamCategory}</p>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from "react";
|
||||
import {
|
||||
ArrowLeft as ArrowLeftIcon,
|
||||
ArrowRight as ArrowRightIcon,
|
||||
} from "lucide-react";
|
||||
import React, {
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "../../assets/styles/listRow.css";
|
||||
import ListItem, { ListItemProps } from "./ListItem";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface ListRowProps {
|
||||
variant?: "default" | "search";
|
||||
@@ -14,7 +19,7 @@ interface ListRowProps {
|
||||
description?: string;
|
||||
items: ListItemProps[];
|
||||
wrap?: boolean;
|
||||
onClick: (itemName: string) => void;
|
||||
onItemClick: (itemName: string) => void;
|
||||
titleClickable?: boolean;
|
||||
extraClasses?: string;
|
||||
itemExtraClasses?: string;
|
||||
@@ -22,8 +27,27 @@ interface ListRowProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }, ListRowProps>(
|
||||
({ variant, type, title = "", description = "", items, wrap, onClick, titleClickable, extraClasses = "", itemExtraClasses = "", amountForScroll, children }, ref) => {
|
||||
const ListRow = forwardRef<
|
||||
{ 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 slider = useRef<HTMLDivElement>(null);
|
||||
const scrollAmount = window.innerWidth * 0.3;
|
||||
@@ -79,7 +103,9 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }
|
||||
>
|
||||
<h2
|
||||
className={`${
|
||||
titleClickable ? "cursor-pointer hover:underline" : "cursor-default"
|
||||
titleClickable
|
||||
? "cursor-pointer hover:underline"
|
||||
: "cursor-default"
|
||||
} text-2xl font-bold`}
|
||||
onClick={titleClickable ? () => handleTitleClick(type) : undefined}
|
||||
>
|
||||
@@ -90,7 +116,7 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }
|
||||
|
||||
{/* List Items */}
|
||||
<div className="relative overflow-hidden flex flex-grow items-center z-0">
|
||||
{!wrap && currentItems.length > (amountForScroll || 0) && (
|
||||
{!wrap && currentItems.length > amountForScroll && (
|
||||
<>
|
||||
<ArrowLeftIcon
|
||||
onClick={slideLeft}
|
||||
@@ -126,10 +152,10 @@ const ListRow = forwardRef<{ addMoreItems: (newItems: ListItemProps[]) => void }
|
||||
onItemClick={() =>
|
||||
(item.type === "stream" || item.type === "user") &&
|
||||
item.username
|
||||
? onClick?.(item.username)
|
||||
: onClick?.(item.title)
|
||||
? onItemClick?.(item.username)
|
||||
: onItemClick?.(item.title)
|
||||
}
|
||||
extraClasses={`${itemExtraClasses} min-w-[20vw] max-w-[20vw]`}
|
||||
extraClasses={`${itemExtraClasses} w-[20vw]`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -145,12 +145,12 @@ const Navbar: React.FC<NavbarProps> = ({ variant = "default" }) => {
|
||||
<SearchBar />
|
||||
|
||||
{/* Stream Button */}
|
||||
{isLoggedIn && !window.location.pathname.includes('go-live') && (
|
||||
{isLoggedIn && !window.location.pathname.includes("go-live") && (
|
||||
<Button
|
||||
extraClasses={`${
|
||||
variant === "home" ? "absolute top-[2vh] right-[10vw]" : ""
|
||||
} 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" />
|
||||
Go Live
|
||||
|
||||
@@ -85,22 +85,30 @@ const Sidebar: React.FC<SideBarProps> = ({ extraClasses }) => {
|
||||
className="font-black text-[1.4rem] hover:underline"
|
||||
onClick={() => navigate(`/user/${username}`)}
|
||||
>
|
||||
<div className="text-[var(--sideBar-profile-text)]">
|
||||
{username}
|
||||
</div>
|
||||
<div className="text-[var(--sideBar-profile-text)]">{username}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="following" className="flex flex-col flex-grow justify-evenly gap-4 p-[1rem]">
|
||||
<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"}
|
||||
<div
|
||||
id="following"
|
||||
className="flex flex-col flex-grow justify-evenly gap-4 p-[1rem]"
|
||||
>
|
||||
<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 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">
|
||||
{followedStreamers.map((streamer) => (
|
||||
<li
|
||||
@@ -115,7 +123,9 @@ const Sidebar: React.FC<SideBarProps> = ({ extraClasses }) => {
|
||||
</div>
|
||||
|
||||
<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">
|
||||
{followedCategories.map((category) => (
|
||||
<li
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import ThemeSetting from "./ThemeSetting";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
import { useQuickSettings } from "../../context/QuickSettingsContext";
|
||||
import Screenshot from "../Functionality/Screenshot"
|
||||
import Screenshot from "../Functionality/Screenshot";
|
||||
import BrightnessControl from "../Functionality/BrightnessControl";
|
||||
|
||||
const QuickSettings: React.FC = () => {
|
||||
@@ -11,19 +11,24 @@ const QuickSettings: React.FC = () => {
|
||||
|
||||
return (
|
||||
<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]"
|
||||
} transition-all duration-300 ease-in-out pt-0 bg-[var(--quickBar-bg)] text-[var(--quickBar-text)]`}
|
||||
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]"
|
||||
} 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)]
|
||||
border-b-[0.25em] border-[var(--quickBar-border)] ">
|
||||
<div
|
||||
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>
|
||||
</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 />
|
||||
</div>
|
||||
<Screenshot />
|
||||
<BrightnessControl />
|
||||
|
||||
<Screenshot />
|
||||
<BrightnessControl />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -140,12 +140,13 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
>
|
||||
{/* User avatar with image */}
|
||||
<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={() =>
|
||||
msg.chatter_username === username
|
||||
? null
|
||||
: window.location.href = `/user/${msg.chatter_username}`
|
||||
: (window.location.href = `/user/${msg.chatter_username}`)
|
||||
}
|
||||
>
|
||||
<img
|
||||
@@ -160,28 +161,33 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
<div className="flex items-center space-x-0.5em">
|
||||
{/* Username */}
|
||||
<span
|
||||
className={`font-bold text-[1em] ${msg.chatter_username === username
|
||||
className={`font-bold text-[1em] ${
|
||||
msg.chatter_username === username
|
||||
? "text-purple-600"
|
||||
: "text-green-400 cursor-pointer"
|
||||
}`}
|
||||
}`}
|
||||
onClick={() =>
|
||||
msg.chatter_username === username
|
||||
? null
|
||||
: window.location.href = `/user/${msg.chatter_username}`
|
||||
: (window.location.href = `/user/${msg.chatter_username}`)
|
||||
}
|
||||
>
|
||||
{msg.chatter_username}
|
||||
</span>
|
||||
</div>
|
||||
{/* 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}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Time sent */}
|
||||
<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>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react";
|
||||
|
||||
interface ThumbnailProps {
|
||||
path: string;
|
||||
alt?: string;
|
||||
|
||||
@@ -32,7 +32,6 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||
const setupPlayer = async () => {
|
||||
const streamKey = await fetchStreamKey();
|
||||
const streamUrl = `/stream/${streamKey}/index.m3u8`;
|
||||
console.log("Player created with src:", streamUrl);
|
||||
|
||||
if (!playerRef.current) {
|
||||
const videoElement = document.createElement("video");
|
||||
|
||||
@@ -4,15 +4,19 @@ import { useBrightness } from "../../context/BrightnessContext";
|
||||
const BrightnessControl: React.FC = () => {
|
||||
const { brightness, setBrightness } = useBrightness();
|
||||
|
||||
const handleBrightnessChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
{/* Set brightness based on the value. Calls BrightnessContext too */}
|
||||
const handleBrightnessChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
{
|
||||
/* Set brightness based on the value. Calls BrightnessContext too */
|
||||
}
|
||||
setBrightness(Number(event.target.value));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center p-4">
|
||||
<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
|
||||
type="range"
|
||||
min="0"
|
||||
|
||||
@@ -5,9 +5,13 @@ interface BrightnessContextType {
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -52,15 +52,18 @@ export function ContentProvider({ children }: { children: React.ReactNode }) {
|
||||
fetch(streamsUrl)
|
||||
.then((response) => response.json())
|
||||
.then((data: any[]) => {
|
||||
const processedStreams: StreamItem[] = data.map(stream => ({
|
||||
const processedStreams: StreamItem[] = data.map((stream) => ({
|
||||
type: "stream",
|
||||
id: stream.user_id,
|
||||
title: stream.title,
|
||||
streamer: stream.username,
|
||||
streamCategory: stream.category_name,
|
||||
viewers: stream.num_viewers,
|
||||
thumbnail: stream.thumbnail ||
|
||||
`/images/category_thumbnails/${stream.category_name.toLowerCase().replace(/ /g, "_")}.webp`
|
||||
thumbnail:
|
||||
stream.thumbnail ||
|
||||
`/images/category_thumbnails/${stream.category_name
|
||||
.toLowerCase()
|
||||
.replace(/ /g, "_")}.webp`,
|
||||
}));
|
||||
setStreams(processedStreams);
|
||||
});
|
||||
@@ -73,12 +76,14 @@ export function ContentProvider({ children }: { children: React.ReactNode }) {
|
||||
fetch(categoriesUrl)
|
||||
.then((response) => response.json())
|
||||
.then((data: any[]) => {
|
||||
const processedCategories: CategoryItem[] = data.map(category => ({
|
||||
const processedCategories: CategoryItem[] = data.map((category) => ({
|
||||
type: "category",
|
||||
id: category.category_id,
|
||||
title: category.category_name,
|
||||
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);
|
||||
});
|
||||
@@ -114,7 +119,10 @@ export function useCategories() {
|
||||
if (!context) {
|
||||
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() {
|
||||
|
||||
@@ -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
|
||||
interface ThemeContextType {
|
||||
theme: string;
|
||||
setTheme: (theme: string) => void;
|
||||
theme: string;
|
||||
setTheme: (theme: string) => void;
|
||||
}
|
||||
|
||||
// Store theme and provide access to setTheme function
|
||||
@@ -11,44 +17,45 @@ interface ThemeContextType {
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
|
||||
// Set default theme to dark
|
||||
// Set default theme to dark
|
||||
|
||||
const [theme, setTheme] = useState<string>(() => {
|
||||
// If exist on user cache, use that instead
|
||||
return localStorage.getItem("user-theme") || "dark";
|
||||
});
|
||||
const [theme, setTheme] = useState<string>(() => {
|
||||
// If exist on user cache, use that instead
|
||||
return localStorage.getItem("user-theme") || "dark";
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Store current theme set by user
|
||||
localStorage.setItem("user-theme", theme);
|
||||
useEffect(() => {
|
||||
// Store current theme set by user
|
||||
localStorage.setItem("user-theme", theme);
|
||||
|
||||
// Update the theme
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
}, [theme]);
|
||||
// Update the theme
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
// Sets the selected theme to child component
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
return (
|
||||
// Sets the selected theme to child component
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Custom Hook which allows any component to access theme & setTheme with "useTheme()"
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext); //Retrieves current value of context
|
||||
if (!context) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider"); //If called outside of ThemeContext.tsx, errorHandle
|
||||
}
|
||||
return context;
|
||||
const context = useContext(ThemeContext); //Retrieves current value of context
|
||||
if (!context) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider"); //If called outside of ThemeContext.tsx, errorHandle
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
{/**
|
||||
{
|
||||
/**
|
||||
|
||||
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")
|
||||
useState: Manages state of a component ("Hook")
|
||||
ReactNode: Allows to take in HTML / React / Arrays of Component
|
||||
|
||||
*/}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export function useCategoryFollow() {
|
||||
}
|
||||
};
|
||||
|
||||
const followCategory = async (categoryName: number) => {
|
||||
const followCategory = async (categoryName: string) => {
|
||||
if (!isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export function useCategoryFollow() {
|
||||
}
|
||||
};
|
||||
|
||||
const unfollowCategory = async (categoryName: number) => {
|
||||
const unfollowCategory = async (categoryName: string) => {
|
||||
if (!isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ import App from "./App.tsx";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<ThemeProvider>
|
||||
<App />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,9 @@ const AllCategoriesPage: React.FC = () => {
|
||||
isLoading.current = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/categories/popular/${noCategories}/${categoryOffset}`);
|
||||
const response = await fetch(
|
||||
`/api/categories/popular/${noCategories}/${categoryOffset}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch categories");
|
||||
}
|
||||
@@ -39,7 +41,7 @@ const AllCategoriesPage: React.FC = () => {
|
||||
return [];
|
||||
}
|
||||
|
||||
setCategoryOffset(prev => prev + data.length);
|
||||
setCategoryOffset((prev) => prev + data.length);
|
||||
|
||||
const processedCategories = data.map((category: any) => ({
|
||||
type: "category" as const,
|
||||
@@ -51,7 +53,7 @@ const AllCategoriesPage: React.FC = () => {
|
||||
.replace(/ /g, "_")}.webp`,
|
||||
}));
|
||||
|
||||
setCategories(prev => [...prev, ...processedCategories]);
|
||||
setCategories((prev) => [...prev, ...processedCategories]);
|
||||
return processedCategories;
|
||||
} catch (error) {
|
||||
console.error("Error fetching categories:", error);
|
||||
@@ -99,7 +101,7 @@ const AllCategoriesPage: React.FC = () => {
|
||||
type="category"
|
||||
title="All Categories"
|
||||
items={categories}
|
||||
onClick={handleCategoryClick}
|
||||
onItemClick={handleCategoryClick}
|
||||
extraClasses="bg-[var(--recommend)] text-center"
|
||||
wrap={true}
|
||||
/>
|
||||
|
||||
@@ -6,16 +6,7 @@ import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll";
|
||||
import Button from "../components/Input/Button";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { useCategoryFollow } from "../hooks/useCategoryFollow";
|
||||
|
||||
interface StreamData {
|
||||
type: "stream";
|
||||
id: number;
|
||||
title: string;
|
||||
streamer: string;
|
||||
streamCategory: string;
|
||||
viewers: number;
|
||||
thumbnail?: string;
|
||||
}
|
||||
import { ListItemProps as StreamData } from "../components/Layout/ListItem";
|
||||
|
||||
const CategoryPage: React.FC = () => {
|
||||
const { categoryName } = useParams<{ categoryName: string }>();
|
||||
@@ -26,10 +17,15 @@ const CategoryPage: React.FC = () => {
|
||||
const [noStreams, setNoStreams] = useState(12);
|
||||
const [hasMoreData, setHasMoreData] = useState(true);
|
||||
const { isLoggedIn } = useAuth();
|
||||
const { isCategoryFollowing, checkCategoryFollowStatus, followCategory, unfollowCategory } = useCategoryFollow()
|
||||
const {
|
||||
isCategoryFollowing,
|
||||
checkCategoryFollowStatus,
|
||||
followCategory,
|
||||
unfollowCategory,
|
||||
} = useCategoryFollow();
|
||||
|
||||
useEffect(() => {
|
||||
checkCategoryFollowStatus(categoryName);
|
||||
if (categoryName) checkCategoryFollowStatus(categoryName);
|
||||
}, [categoryName]);
|
||||
|
||||
const fetchCategoryStreams = async () => {
|
||||
@@ -38,7 +34,9 @@ const CategoryPage: React.FC = () => {
|
||||
|
||||
isLoading.current = true;
|
||||
try {
|
||||
const response = await fetch(`/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}`);
|
||||
const response = await fetch(
|
||||
`/api/streams/popular/${categoryName}/${noStreams}/${streamOffset}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch category streams");
|
||||
}
|
||||
@@ -49,13 +47,13 @@ const CategoryPage: React.FC = () => {
|
||||
return [];
|
||||
}
|
||||
|
||||
setStreamOffset(prev => prev + data.length);
|
||||
setStreamOffset((prev) => prev + data.length);
|
||||
|
||||
const processedStreams = data.map((stream: any) => ({
|
||||
type: "stream",
|
||||
id: stream.user_id,
|
||||
title: stream.title,
|
||||
streamer: stream.username,
|
||||
username: stream.username,
|
||||
streamCategory: categoryName,
|
||||
viewers: stream.num_viewers,
|
||||
thumbnail:
|
||||
@@ -66,8 +64,8 @@ const CategoryPage: React.FC = () => {
|
||||
.replace(/ /g, "_")}.webp`),
|
||||
}));
|
||||
|
||||
setStreams(prev => [...prev, ...processedStreams]);
|
||||
return processedStreams
|
||||
setStreams((prev) => [...prev, ...processedStreams]);
|
||||
return processedStreams;
|
||||
} catch (error) {
|
||||
console.error("Error fetching category streams:", error);
|
||||
} finally {
|
||||
@@ -90,7 +88,6 @@ const CategoryPage: React.FC = () => {
|
||||
|
||||
fetchContentOnScroll(logOnScroll, hasMoreData);
|
||||
|
||||
|
||||
const handleStreamClick = (streamerName: string) => {
|
||||
window.location.href = `/${streamerName}`;
|
||||
};
|
||||
@@ -115,14 +112,18 @@ const CategoryPage: React.FC = () => {
|
||||
description={`Live streams in the ${categoryName} category`}
|
||||
items={streams}
|
||||
wrap={true}
|
||||
onClick={handleStreamClick}
|
||||
onItemClick={handleStreamClick}
|
||||
extraClasses="bg-[var(--recommend)]"
|
||||
>
|
||||
{isLoggedIn && (
|
||||
<Button
|
||||
extraClasses="absolute right-10"
|
||||
onClick={() => {
|
||||
isCategoryFollowing ? unfollowCategory(categoryName) : followCategory(categoryName)
|
||||
if (categoryName) {
|
||||
isCategoryFollowing
|
||||
? unfollowCategory(categoryName)
|
||||
: followCategory(categoryName);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isCategoryFollowing ? "Unfollow" : "Follow"}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import React from "react";
|
||||
import ListRow from "../components/Layout/ListRow";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useStreams, useCategories } from "../context/ContentContext";
|
||||
@@ -45,7 +45,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||
}
|
||||
items={streams}
|
||||
wrap={false}
|
||||
onClick={handleStreamClick}
|
||||
onItemClick={handleStreamClick}
|
||||
extraClasses="bg-[var(--liveNow)]"
|
||||
/>
|
||||
|
||||
@@ -64,7 +64,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||
}
|
||||
items={categories}
|
||||
wrap={false}
|
||||
onClick={handleCategoryClick}
|
||||
onItemClick={handleCategoryClick}
|
||||
titleClickable={true}
|
||||
extraClasses="bg-[var(--recommend)]"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,9 @@ import Button from "../components/Input/Button";
|
||||
import ChromeDinoGame from "react-chrome-dino";
|
||||
|
||||
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 [score, setScore] = useState(0);
|
||||
@@ -16,9 +18,15 @@ const NotFoundPage: React.FC = () => {
|
||||
const loop = setInterval(() => {
|
||||
if (Math.random() < 0.1) {
|
||||
const newStar = {
|
||||
x: score > 20000 ? (window.innerWidth + starSize) : Math.random() * (window.innerWidth - starSize),
|
||||
y: score > 20000 ? Math.random() * (window.innerHeight - starSize) : -starSize,
|
||||
xChange: score * .001,
|
||||
x:
|
||||
score > 20000
|
||||
? window.innerWidth + starSize
|
||||
: Math.random() * (window.innerWidth - starSize),
|
||||
y:
|
||||
score > 20000
|
||||
? Math.random() * (window.innerHeight - starSize)
|
||||
: -starSize,
|
||||
xChange: score * 0.001,
|
||||
yChange: 5,
|
||||
};
|
||||
setStars((prev) => [...prev, newStar]);
|
||||
@@ -39,7 +47,7 @@ const NotFoundPage: React.FC = () => {
|
||||
return newStars.map((star) => ({
|
||||
x: star.x - star.xChange,
|
||||
y: star.y + star.yChange,
|
||||
xChange: score * .001,
|
||||
xChange: score * 0.001,
|
||||
yChange: star.yChange,
|
||||
}));
|
||||
});
|
||||
@@ -68,7 +76,15 @@ const NotFoundPage: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
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>
|
||||
{stars.map((star, index) => (
|
||||
<div
|
||||
@@ -81,7 +97,11 @@ const NotFoundPage: React.FC = () => {
|
||||
))}
|
||||
</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={`${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>
|
||||
<p className="text-2xl mb-8">Page Not Found</p>
|
||||
<ChromeDinoGame />
|
||||
|
||||
@@ -3,28 +3,29 @@ import PasswordResetForm from "../components/Auth/PasswordResetForm";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const ResetPasswordPage: React.FC = () => {
|
||||
const { token } = useParams<{ token: string }>();
|
||||
const { token } = useParams<{ token: string }>();
|
||||
|
||||
const handlePasswordReset = (success: boolean) => {
|
||||
if (success) {
|
||||
alert("Password reset successful!");
|
||||
window.location.href = "/";
|
||||
}
|
||||
else {
|
||||
alert("Password reset failed.");
|
||||
}
|
||||
};
|
||||
|
||||
if (!token) {
|
||||
return <p className="text-red-500 text-center mt-4">Invalid or missing token.</p>;
|
||||
const handlePasswordReset = (success: boolean) => {
|
||||
if (success) {
|
||||
alert("Password reset successful!");
|
||||
window.location.href = "/";
|
||||
} else {
|
||||
alert("Password reset failed.");
|
||||
}
|
||||
};
|
||||
|
||||
if (!token) {
|
||||
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>
|
||||
<p className="text-red-500 text-center mt-4">Invalid or missing token.</p>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -70,7 +70,7 @@ const ResultsPage: React.FC = ({}) => {
|
||||
thumbnail: stream.thumbnail_url,
|
||||
}))}
|
||||
title="Streams"
|
||||
onClick={(streamer_name: string) =>
|
||||
onItemClick={(streamer_name: string) =>
|
||||
(window.location.href = `/${streamer_name}`)
|
||||
}
|
||||
itemExtraClasses="min-w-[calc(12vw+12vh/2)]"
|
||||
@@ -92,7 +92,7 @@ const ResultsPage: React.FC = ({}) => {
|
||||
.replace(/ /g, "_")}.webp`,
|
||||
}))}
|
||||
title="Categories"
|
||||
onClick={(category_name: string) =>
|
||||
onItemClick={(category_name: string) =>
|
||||
navigate(`/category/${category_name}`)
|
||||
}
|
||||
titleClickable={true}
|
||||
@@ -114,7 +114,7 @@ const ResultsPage: React.FC = ({}) => {
|
||||
thumbnail: user.profile_picture,
|
||||
}))}
|
||||
title="Users"
|
||||
onClick={(username: string) =>
|
||||
onItemClick={(username: string) =>
|
||||
(window.location.href = `/user/${username}`)
|
||||
}
|
||||
amountForScroll={3}
|
||||
|
||||
@@ -461,7 +461,7 @@ const StreamDashboardPage: React.FC = () => {
|
||||
type="stream"
|
||||
id={1}
|
||||
title={streamData.title || "Stream Title"}
|
||||
streamer={username || ""}
|
||||
username={username || ""}
|
||||
streamCategory={streamData.category_name || "Category"}
|
||||
viewers={streamData.viewer_count}
|
||||
thumbnail={thumbnailPreview.url || ""}
|
||||
|
||||
@@ -116,7 +116,6 @@ const UserPage: React.FC = () => {
|
||||
} 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="grid grid-cols-4 grid-rows-[0.1fr_4fr] w-full gap-8">
|
||||
{/* Profile Section - TOP */}
|
||||
|
||||
@@ -129,9 +128,9 @@ const UserPage: React.FC = () => {
|
||||
{/* Border Overlay (Always on Top) */}
|
||||
<div className="absolute left-[0px] inset-0 border-[5px] border-[var(--user-borderBg)] rounded-[20px] z-20"></div>
|
||||
|
||||
|
||||
{/* 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"
|
||||
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"
|
||||
>
|
||||
{/* 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">
|
||||
<h2 className="text-xl font-semibold mb-3">
|
||||
@@ -258,7 +259,6 @@ const UserPage: React.FC = () => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -269,31 +269,39 @@ const UserPage: React.FC = () => {
|
||||
<div
|
||||
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"
|
||||
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "var(--follow-shadow)"}
|
||||
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"}
|
||||
onMouseEnter={(e) =>
|
||||
(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
|
||||
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"
|
||||
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "var(--follow-shadow)"}
|
||||
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"}
|
||||
onMouseEnter={(e) =>
|
||||
(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
|
||||
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"
|
||||
onMouseEnter={(e) => e.currentTarget.style.boxShadow = "var(--follow-shadow)"}
|
||||
onMouseLeave={(e) => e.currentTarget.style.boxShadow = "none"}
|
||||
onMouseEnter={(e) =>
|
||||
(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>
|
||||
|
||||
Reference in New Issue
Block a user