mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Added ProfileMenu
Bit of refactoring
This commit is contained in:
+14
-16
@@ -1,25 +1,23 @@
|
||||
import router from 'Frontend/routes.js';
|
||||
import {AuthProvider} from 'Frontend/util/auth.js';
|
||||
import {RouterProvider} from 'react-router-dom';
|
||||
import {Outlet, useNavigate} from 'react-router-dom';
|
||||
import "./main.css";
|
||||
import {IconContext} from "@phosphor-icons/react";
|
||||
import {StrictMode} from "react";
|
||||
import {NextUIProvider} from "@nextui-org/react";
|
||||
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
||||
import {themeNames} from "Frontend/theming/themes";
|
||||
import {AuthProvider} from "Frontend/util/auth";
|
||||
import {IconContext} from "@phosphor-icons/react";
|
||||
|
||||
export default function App() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<StrictMode>
|
||||
<NextUIProvider className="size-full">
|
||||
<NextThemesProvider attribute="class" themes={themeNames()} defaultTheme="gameyfin-violet-dark" >
|
||||
<AuthProvider>
|
||||
<IconContext.Provider value={{size: 20}}>
|
||||
<RouterProvider router={router}/>
|
||||
</IconContext.Provider>
|
||||
</AuthProvider>
|
||||
</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
</StrictMode>
|
||||
<NextUIProvider className="size-full" navigate={navigate}>
|
||||
<NextThemesProvider attribute="class" themes={themeNames()} defaultTheme="gameyfin-violet-dark">
|
||||
<AuthProvider>
|
||||
<IconContext.Provider value={{size: 20}}>
|
||||
<Outlet/>
|
||||
</IconContext.Provider>
|
||||
</AuthProvider>
|
||||
</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import {useAuth} from "Frontend/util/auth";
|
||||
import {GearFine, Question, SignOut, User} from "@phosphor-icons/react";
|
||||
import {Avatar, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@nextui-org/react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
export default function ProfileMenu() {
|
||||
const {state, logout} = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const profileMenuItems = [
|
||||
{
|
||||
label: "My Profile",
|
||||
icon: <User/>,
|
||||
onClick: () => alert("Profile")
|
||||
onClick: () => navigate('/profile')
|
||||
},
|
||||
{
|
||||
label: "Administration",
|
||||
@@ -26,7 +28,7 @@ export default function ProfileMenu() {
|
||||
label: "Sign Out",
|
||||
icon: <SignOut/>,
|
||||
onClick: () => logout(),
|
||||
color: "danger"
|
||||
color: "primary"
|
||||
},
|
||||
];
|
||||
|
||||
@@ -38,7 +40,7 @@ export default function ProfileMenu() {
|
||||
as="button"
|
||||
className="transition-transform size-8"
|
||||
classNames={{
|
||||
base: "bg-gradient-to-br from-primary-400 to-primary-700",
|
||||
base: "gradient-primary",
|
||||
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>
|
||||
);
|
||||
+7
-1
@@ -1,3 +1,9 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.gradient-primary {
|
||||
@apply bg-gradient-to-br from-primary-400 to-primary-700;
|
||||
}
|
||||
}
|
||||
+27
-9
@@ -4,20 +4,38 @@ import LoginView from "Frontend/views/LoginView";
|
||||
import MainLayout from "Frontend/views/MainLayout";
|
||||
import TestView from "Frontend/views/TestView";
|
||||
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([
|
||||
{
|
||||
element: <MainLayout/>,
|
||||
handle: {requiresLogin: true},
|
||||
element: <App/>,
|
||||
handle: {requiresLogin: false},
|
||||
children: [
|
||||
{path: '/', element: <TestView/>, handle: {title: 'Gameyfin - Test'}},
|
||||
{
|
||||
element: <MainLayout/>,
|
||||
handle: {requiresLogin: true},
|
||||
children: [
|
||||
{
|
||||
index: true, element: <TestView/>
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
element: <ProfileView/>,
|
||||
children: [
|
||||
{path: 'appearance', element: <ThemeSelector/>}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login', element: <LoginView/>, handle: {requiresLogin: false}
|
||||
},
|
||||
{
|
||||
path: '/setup', element: <SetupView/>, handle: {requiresLogin: false}
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login', element: <LoginView/>, handle: {requiresLogin: false}
|
||||
},
|
||||
{
|
||||
path: '/setup', element: <SetupView/>, handle: {requiresLogin: false}
|
||||
}
|
||||
]) as RouteObject[];
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {useAuth} from "Frontend/util/auth";
|
||||
import {useEffect, useLayoutEffect, useState} from "react";
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import {useLayoutEffect, useState} from "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 {useNavigate} from "react-router-dom";
|
||||
|
||||
export default function LoginView() {
|
||||
const {state, login} = useAuth();
|
||||
@@ -16,13 +16,13 @@ export default function LoginView() {
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (state.user) {
|
||||
const path = url ? new URL(url, document.baseURI).pathname : '/'
|
||||
const path = url ? new URL(url, document.baseURI).pathname : '/'
|
||||
navigate(path, {replace: true});
|
||||
}
|
||||
}, [state.user]);
|
||||
|
||||
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">
|
||||
<CardHeader>
|
||||
<img
|
||||
@@ -84,7 +84,7 @@ export default function LoginView() {
|
||||
placeholder=""
|
||||
/>
|
||||
<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}>
|
||||
{loading ? "" : "Log in"}
|
||||
</Button>
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import {useRouteMetadata} from 'Frontend/util/routing.js';
|
||||
import {useEffect} from 'react';
|
||||
import ProfileMenu from "Frontend/components/ProfileMenu";
|
||||
import {Outlet} from "react-router-dom";
|
||||
import {Navbar, NavbarBrand, NavbarContent, NavbarItem} from "@nextui-org/react";
|
||||
import {Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem} from "@nextui-org/react";
|
||||
import GameyfinLogo from "Frontend/components/theming/GameyfinLogo";
|
||||
import * as PackageJson from "../../package.json";
|
||||
import {Outlet, useNavigate} from "react-router-dom";
|
||||
|
||||
export default function MainLayout() {
|
||||
const currentTitle = `Gameyfin - ${useRouteMetadata()?.title}` ?? 'Gameyfin';
|
||||
useEffect(() => {
|
||||
document.title = currentTitle;
|
||||
}, [currentTitle]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col min-h-svh">
|
||||
<Navbar maxWidth="2xl" className="shadow">
|
||||
<NavbarBrand>
|
||||
<NavbarBrand as="button" onClick={() => navigate('/')}>
|
||||
<GameyfinLogo className="h-10 fill-foreground"/>
|
||||
</NavbarBrand>
|
||||
<NavbarContent justify="end">
|
||||
@@ -24,19 +26,25 @@ export default function MainLayout() {
|
||||
</NavbarContent>
|
||||
</Navbar>
|
||||
|
||||
<Outlet/>
|
||||
</>
|
||||
<div className="flex-1">
|
||||
<div className="flex-row relative m-auto max-w-[1536px] align-self-center px-2 pt-4">
|
||||
<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 Wizard from "Frontend/components/wizard/Wizard";
|
||||
import WizardStep from "Frontend/components/wizard/WizardStep";
|
||||
import Input from "Frontend/components/Input";
|
||||
import {GearFine, HandWaving, Moon, Palette, SunDim, User} from "@phosphor-icons/react";
|
||||
import ThemePreview from "Frontend/components/theming/ThemePreview";
|
||||
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 {GearFine, HandWaving, Palette, User} from "@phosphor-icons/react";
|
||||
import {Card} from "@nextui-org/react";
|
||||
import {UserEndpoint} from "Frontend/generated/endpoints";
|
||||
import {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
|
||||
|
||||
function WelcomeStep() {
|
||||
return (
|
||||
@@ -36,50 +33,9 @@ function WelcomeStep() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<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>
|
||||
)
|
||||
<ThemeSelector/>
|
||||
);
|
||||
}
|
||||
|
||||
function UserStep() {
|
||||
@@ -127,7 +83,7 @@ function SettingsStep() {
|
||||
}
|
||||
|
||||
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">
|
||||
<Wizard
|
||||
initialValues={{username: '', email: '', password: '', passwordRepeat: ''}}
|
||||
|
||||
Reference in New Issue
Block a user