mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Added ProfileMenu
Bit of refactoring
This commit is contained in:
+7
-9
@@ -1,25 +1,23 @@
|
|||||||
import router from 'Frontend/routes.js';
|
import {Outlet, useNavigate} from 'react-router-dom';
|
||||||
import {AuthProvider} from 'Frontend/util/auth.js';
|
|
||||||
import {RouterProvider} from 'react-router-dom';
|
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
import {IconContext} from "@phosphor-icons/react";
|
|
||||||
import {StrictMode} from "react";
|
|
||||||
import {NextUIProvider} from "@nextui-org/react";
|
import {NextUIProvider} from "@nextui-org/react";
|
||||||
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
||||||
import {themeNames} from "Frontend/theming/themes";
|
import {themeNames} from "Frontend/theming/themes";
|
||||||
|
import {AuthProvider} from "Frontend/util/auth";
|
||||||
|
import {IconContext} from "@phosphor-icons/react";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StrictMode>
|
<NextUIProvider className="size-full" navigate={navigate}>
|
||||||
<NextUIProvider className="size-full">
|
|
||||||
<NextThemesProvider attribute="class" themes={themeNames()} defaultTheme="gameyfin-violet-dark">
|
<NextThemesProvider attribute="class" themes={themeNames()} defaultTheme="gameyfin-violet-dark">
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<IconContext.Provider value={{size: 20}}>
|
<IconContext.Provider value={{size: 20}}>
|
||||||
<RouterProvider router={router}/>
|
<Outlet/>
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</NextThemesProvider>
|
</NextThemesProvider>
|
||||||
</NextUIProvider>
|
</NextUIProvider>
|
||||||
</StrictMode>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import {useAuth} from "Frontend/util/auth";
|
import {useAuth} from "Frontend/util/auth";
|
||||||
import {GearFine, Question, SignOut, User} from "@phosphor-icons/react";
|
import {GearFine, Question, SignOut, User} from "@phosphor-icons/react";
|
||||||
import {Avatar, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@nextui-org/react";
|
import {Avatar, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@nextui-org/react";
|
||||||
|
import {useNavigate} from "react-router-dom";
|
||||||
|
|
||||||
export default function ProfileMenu() {
|
export default function ProfileMenu() {
|
||||||
const {state, logout} = useAuth();
|
const {state, logout} = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const profileMenuItems = [
|
const profileMenuItems = [
|
||||||
{
|
{
|
||||||
label: "My Profile",
|
label: "My Profile",
|
||||||
icon: <User/>,
|
icon: <User/>,
|
||||||
onClick: () => alert("Profile")
|
onClick: () => navigate('/profile')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Administration",
|
label: "Administration",
|
||||||
@@ -26,7 +28,7 @@ export default function ProfileMenu() {
|
|||||||
label: "Sign Out",
|
label: "Sign Out",
|
||||||
icon: <SignOut/>,
|
icon: <SignOut/>,
|
||||||
onClick: () => logout(),
|
onClick: () => logout(),
|
||||||
color: "danger"
|
color: "primary"
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ export default function ProfileMenu() {
|
|||||||
as="button"
|
as="button"
|
||||||
className="transition-transform size-8"
|
className="transition-transform size-8"
|
||||||
classNames={{
|
classNames={{
|
||||||
base: "bg-gradient-to-br from-primary-400 to-primary-700",
|
base: "gradient-primary",
|
||||||
icon: "text-background/80"
|
icon: "text-background/80"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import {useTheme} from "next-themes";
|
||||||
|
import React, {useLayoutEffect, useState} from "react";
|
||||||
|
import {Switch} from "@nextui-org/react";
|
||||||
|
import {Moon, SunDim} from "@phosphor-icons/react";
|
||||||
|
import {themes} from "Frontend/theming/themes";
|
||||||
|
import {Theme} from "Frontend/theming/theme";
|
||||||
|
import ThemePreview from "Frontend/components/theming/ThemePreview";
|
||||||
|
|
||||||
|
export function ThemeSelector() {
|
||||||
|
|
||||||
|
const {theme, setTheme} = useTheme();
|
||||||
|
const [isSelected, setIsSelected] = useState(theme ? theme.includes("light") : false);
|
||||||
|
const [currentTheme, setCurrentTheme] = useState(theme?.substring(0, theme?.lastIndexOf("-")));
|
||||||
|
|
||||||
|
useLayoutEffect(() => setTheme(`${currentTheme}-${mode()}`), [isSelected]);
|
||||||
|
|
||||||
|
function mode(): "light" | "dark" {
|
||||||
|
return isSelected ? "light" : "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col size-full items-center">
|
||||||
|
<div className="w-full flex flex-row items-center justify-center gap-4 mb-16">
|
||||||
|
<Switch
|
||||||
|
size="lg"
|
||||||
|
startContent={<SunDim size={32}/>}
|
||||||
|
endContent={<Moon size={32}/>}
|
||||||
|
isSelected={isSelected}
|
||||||
|
onValueChange={() => {
|
||||||
|
setIsSelected(!isSelected);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-flow-col auto-cols-auto auto-cols-max-4 gap-8">
|
||||||
|
{
|
||||||
|
//min-w-[468px]
|
||||||
|
themes.map(((t: Theme) => (
|
||||||
|
<div
|
||||||
|
key={t.name}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentTheme(t.name);
|
||||||
|
setTheme(`${t.name}-${mode()}`);
|
||||||
|
}}>
|
||||||
|
<ThemePreview
|
||||||
|
theme={t}
|
||||||
|
mode={mode()}
|
||||||
|
isSelected={currentTheme === t.name}/>
|
||||||
|
</div>
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import {createRoot} from 'react-dom/client';
|
|
||||||
import {createElement} from "react";
|
|
||||||
import App from "Frontend/App";
|
|
||||||
|
|
||||||
createRoot(document.getElementById('outlet')!).render(createElement(App));
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import {createRoot} from 'react-dom/client';
|
||||||
|
import {StrictMode} from "react";
|
||||||
|
import {RouterProvider} from "react-router-dom";
|
||||||
|
import router from "./routes";
|
||||||
|
|
||||||
|
const container = document.getElementById('outlet')!;
|
||||||
|
const root = createRoot(container);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<RouterProvider router={router}/>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.gradient-primary {
|
||||||
|
@apply bg-gradient-to-br from-primary-400 to-primary-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-2
@@ -4,14 +4,30 @@ import LoginView from "Frontend/views/LoginView";
|
|||||||
import MainLayout from "Frontend/views/MainLayout";
|
import MainLayout from "Frontend/views/MainLayout";
|
||||||
import TestView from "Frontend/views/TestView";
|
import TestView from "Frontend/views/TestView";
|
||||||
import SetupView from "Frontend/views/SetupView";
|
import SetupView from "Frontend/views/SetupView";
|
||||||
|
import ProfileView from "Frontend/views/ProfileView";
|
||||||
|
import {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
|
||||||
|
import App from "Frontend/App";
|
||||||
|
|
||||||
export const routes = protectRoutes([
|
export const routes = protectRoutes([
|
||||||
|
{
|
||||||
|
element: <App/>,
|
||||||
|
handle: {requiresLogin: false},
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
element: <MainLayout/>,
|
element: <MainLayout/>,
|
||||||
handle: {requiresLogin: true},
|
handle: {requiresLogin: true},
|
||||||
children: [
|
children: [
|
||||||
{path: '/', element: <TestView/>, handle: {title: 'Gameyfin - Test'}},
|
{
|
||||||
],
|
index: true, element: <TestView/>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'profile',
|
||||||
|
element: <ProfileView/>,
|
||||||
|
children: [
|
||||||
|
{path: 'appearance', element: <ThemeSelector/>}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login', element: <LoginView/>, handle: {requiresLogin: false}
|
path: '/login', element: <LoginView/>, handle: {requiresLogin: false}
|
||||||
@@ -19,6 +35,8 @@ export const routes = protectRoutes([
|
|||||||
{
|
{
|
||||||
path: '/setup', element: <SetupView/>, handle: {requiresLogin: false}
|
path: '/setup', element: <SetupView/>, handle: {requiresLogin: false}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
]) as RouteObject[];
|
]) as RouteObject[];
|
||||||
|
|
||||||
export default createBrowserRouter(routes);
|
export default createBrowserRouter(routes);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {useAuth} from "Frontend/util/auth";
|
import {useAuth} from "Frontend/util/auth";
|
||||||
import {useEffect, useLayoutEffect, useState} from "react";
|
import {useLayoutEffect, useState} from "react";
|
||||||
import {Link, useNavigate} from "react-router-dom";
|
|
||||||
import {XCircle} from "@phosphor-icons/react";
|
import {XCircle} from "@phosphor-icons/react";
|
||||||
import {Button, Card, CardBody, CardHeader, Input} from "@nextui-org/react";
|
import {Button, Card, CardBody, CardHeader, Input, Link} from "@nextui-org/react";
|
||||||
import {Alert, AlertDescription, AlertTitle} from "Frontend/@/components/ui/alert";
|
import {Alert, AlertDescription, AlertTitle} from "Frontend/@/components/ui/alert";
|
||||||
|
import {useNavigate} from "react-router-dom";
|
||||||
|
|
||||||
export default function LoginView() {
|
export default function LoginView() {
|
||||||
const {state, login} = useAuth();
|
const {state, login} = useAuth();
|
||||||
@@ -22,7 +22,7 @@ export default function LoginView() {
|
|||||||
}, [state.user]);
|
}, [state.user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full bg-gradient-to-br from-primary-400 to-primary-700">
|
<div className="flex size-full gradient-primary">
|
||||||
<Card className="m-auto p-12">
|
<Card className="m-auto p-12">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<img
|
<img
|
||||||
@@ -84,7 +84,7 @@ export default function LoginView() {
|
|||||||
placeholder=""
|
placeholder=""
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Link to="#">Forgot password?</Link>
|
<Link color="foreground" underline="always">Forgot password?</Link>
|
||||||
<Button color="primary" type="submit" isLoading={loading}>
|
<Button color="primary" type="submit" isLoading={loading}>
|
||||||
{loading ? "" : "Log in"}
|
{loading ? "" : "Log in"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import {useRouteMetadata} from 'Frontend/util/routing.js';
|
import {useRouteMetadata} from 'Frontend/util/routing.js';
|
||||||
import {useEffect} from 'react';
|
import {useEffect} from 'react';
|
||||||
import ProfileMenu from "Frontend/components/ProfileMenu";
|
import ProfileMenu from "Frontend/components/ProfileMenu";
|
||||||
import {Outlet} from "react-router-dom";
|
import {Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem} from "@nextui-org/react";
|
||||||
import {Navbar, NavbarBrand, NavbarContent, NavbarItem} from "@nextui-org/react";
|
|
||||||
import GameyfinLogo from "Frontend/components/theming/GameyfinLogo";
|
import GameyfinLogo from "Frontend/components/theming/GameyfinLogo";
|
||||||
|
import * as PackageJson from "../../package.json";
|
||||||
|
import {Outlet, useNavigate} from "react-router-dom";
|
||||||
|
|
||||||
export default function MainLayout() {
|
export default function MainLayout() {
|
||||||
const currentTitle = `Gameyfin - ${useRouteMetadata()?.title}` ?? 'Gameyfin';
|
const currentTitle = `Gameyfin - ${useRouteMetadata()?.title}` ?? 'Gameyfin';
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = currentTitle;
|
document.title = currentTitle;
|
||||||
}, [currentTitle]);
|
}, [currentTitle]);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col min-h-svh">
|
||||||
<Navbar maxWidth="2xl" className="shadow">
|
<Navbar maxWidth="2xl" className="shadow">
|
||||||
<NavbarBrand>
|
<NavbarBrand as="button" onClick={() => navigate('/')}>
|
||||||
<GameyfinLogo className="h-10 fill-foreground"/>
|
<GameyfinLogo className="h-10 fill-foreground"/>
|
||||||
</NavbarBrand>
|
</NavbarBrand>
|
||||||
<NavbarContent justify="end">
|
<NavbarContent justify="end">
|
||||||
@@ -24,19 +26,25 @@ export default function MainLayout() {
|
|||||||
</NavbarContent>
|
</NavbarContent>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex-row relative m-auto max-w-[1536px] align-self-center px-2 pt-4">
|
||||||
<Outlet/>
|
<Outlet/>
|
||||||
</>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider/>
|
||||||
|
|
||||||
|
<footer className="flex flex-row items-center justify-between py-4 px-12">
|
||||||
|
<p>Gameyfin {PackageJson.version}</p>
|
||||||
|
<p>
|
||||||
|
© {(new Date()).getFullYear()} 
|
||||||
|
<Link href="https://github.com/gameyfin/gameyfin/graphs/contributors" target="_blank">
|
||||||
|
Gameyfin contributors
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*<footer
|
/**/
|
||||||
className="flex flex-row w-full items-center justify-between px-10 py-4">
|
|
||||||
<Typography color="blue-gray">
|
|
||||||
Gameyfin v{packageJson.version}
|
|
||||||
</Typography>
|
|
||||||
<Typography color="blue-gray">
|
|
||||||
© {(new Date()).getFullYear()} <a
|
|
||||||
href="https://github.com/gameyfin/gameyfin/graphs/contributors" target="_blank">Gameyfin
|
|
||||||
contributors</a>
|
|
||||||
</Typography>
|
|
||||||
</footer>*/
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import {Listbox, ListboxItem} from "@nextui-org/react";
|
||||||
|
import {GearFine, Palette, User} from "@phosphor-icons/react";
|
||||||
|
import {Outlet, useNavigate} from "react-router-dom";
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
export default function ProfileView() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [selectedKeys, setSelectedKeys] = useState(new Set(["profile"]));
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
title: "My Profile",
|
||||||
|
key: "profile",
|
||||||
|
icon: <User/>,
|
||||||
|
action: () => navigate('/profile')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Appearance",
|
||||||
|
key: "appearance",
|
||||||
|
icon: <Palette/>,
|
||||||
|
action: () => navigate('appearance')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Manage account",
|
||||||
|
icon: <GearFine/>,
|
||||||
|
key: "account-management",
|
||||||
|
action: () => navigate('account-management')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="flex flex-col pr-8">
|
||||||
|
<Listbox className="min-w-60">
|
||||||
|
{menuItems.map((i) => (
|
||||||
|
<ListboxItem key={i.key} onPress={i.action} startContent={i.icon}>
|
||||||
|
{i.title}
|
||||||
|
</ListboxItem>
|
||||||
|
))}
|
||||||
|
</Listbox>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Outlet/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import React, {useLayoutEffect, useState} from 'react';
|
import React from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import Wizard from "Frontend/components/wizard/Wizard";
|
import Wizard from "Frontend/components/wizard/Wizard";
|
||||||
import WizardStep from "Frontend/components/wizard/WizardStep";
|
import WizardStep from "Frontend/components/wizard/WizardStep";
|
||||||
import Input from "Frontend/components/Input";
|
import Input from "Frontend/components/Input";
|
||||||
import {GearFine, HandWaving, Moon, Palette, SunDim, User} from "@phosphor-icons/react";
|
import {GearFine, HandWaving, Palette, User} from "@phosphor-icons/react";
|
||||||
import ThemePreview from "Frontend/components/theming/ThemePreview";
|
import {Card} from "@nextui-org/react";
|
||||||
import {themes} from "Frontend/theming/themes";
|
|
||||||
import {Card, Switch} from "@nextui-org/react";
|
|
||||||
import {useTheme} from "next-themes";
|
|
||||||
import {Theme} from "Frontend/theming/theme";
|
|
||||||
import {UserEndpoint} from "Frontend/generated/endpoints";
|
import {UserEndpoint} from "Frontend/generated/endpoints";
|
||||||
|
import {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
|
||||||
|
|
||||||
function WelcomeStep() {
|
function WelcomeStep() {
|
||||||
return (
|
return (
|
||||||
@@ -36,50 +33,9 @@ function WelcomeStep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ThemeStep() {
|
function ThemeStep() {
|
||||||
const {theme, setTheme} = useTheme();
|
|
||||||
const [isSelected, setIsSelected] = useState(theme ? theme.includes("light") : false);
|
|
||||||
const [currentTheme, setCurrentTheme] = useState(theme?.substring(0, theme?.lastIndexOf("-")));
|
|
||||||
|
|
||||||
useLayoutEffect(() => setTheme(`${currentTheme}-${mode()}`), [isSelected]);
|
|
||||||
|
|
||||||
function mode(): "light" | "dark" {
|
|
||||||
return isSelected ? "light" : "dark";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col size-full items-center">
|
<ThemeSelector/>
|
||||||
<div className="w-full flex flex-row items-center justify-center gap-4 mb-16">
|
);
|
||||||
<Switch
|
|
||||||
size="lg"
|
|
||||||
startContent={<SunDim size={32}/>}
|
|
||||||
endContent={<Moon size={32}/>}
|
|
||||||
isSelected={isSelected}
|
|
||||||
onValueChange={() => {
|
|
||||||
setIsSelected(!isSelected);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-flow-col auto-cols-auto auto-cols-max-4 gap-8">
|
|
||||||
{
|
|
||||||
//min-w-[468px]
|
|
||||||
themes.map(((t: Theme) => (
|
|
||||||
<div
|
|
||||||
key={t.name}
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentTheme(t.name);
|
|
||||||
setTheme(`${t.name}-${mode()}`);
|
|
||||||
}}>
|
|
||||||
<ThemePreview
|
|
||||||
theme={t}
|
|
||||||
mode={mode()}
|
|
||||||
isSelected={currentTheme === t.name}/>
|
|
||||||
</div>
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function UserStep() {
|
function UserStep() {
|
||||||
@@ -127,7 +83,7 @@ function SettingsStep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SetupView = () => (
|
const SetupView = () => (
|
||||||
<div className="flex size-full bg-gradient-to-br from-primary-400 to-primary-700">
|
<div className="flex size-full gradient-primary">
|
||||||
<Card className="w-3/4 h-3/4 min-w-[500px] m-auto p-8">
|
<Card className="w-3/4 h-3/4 min-w-[500px] m-auto p-8">
|
||||||
<Wizard
|
<Wizard
|
||||||
initialValues={{username: '', email: '', password: '', passwordRepeat: ''}}
|
initialValues={{username: '', email: '', password: '', passwordRepeat: ''}}
|
||||||
|
|||||||
Reference in New Issue
Block a user