Update to Hilla 24

This commit is contained in:
grimsi
2024-08-22 10:55:22 +02:00
parent 042c326380
commit cb073c6bcf
52 changed files with 5944 additions and 4078 deletions
+97
View File
@@ -0,0 +1,97 @@
import {useAuth} from "Frontend/util/auth";
import {useLayoutEffect, useState} from "react";
import {XCircle} from "@phosphor-icons/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();
const [hasError, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [username, setUsername] = useState<string>();
const [password, setPassword] = useState<string>();
const [url, setUrl] = useState<string>();
const navigate = useNavigate();
useLayoutEffect(() => {
if (state.user) {
const path = url ? new URL(url, document.baseURI).pathname : '/'
navigate(path, {replace: true});
}
}, [state.user]);
return (
<div className="flex size-full gradient-primary">
<Card className="m-auto p-12">
<CardHeader>
<img
className="h-28 w-full content-center"
src="/images/Logo.svg"
alt="Gameyfin Logo"
/>
</CardHeader>
<CardBody className="mt-8 mb-2 w-80 max-w-screen-lg sm:w-96">
{hasError &&
<Alert className="mb-4" variant="destructive">
<XCircle weight="fill" className="size-4"/>
<AlertTitle>Error</AlertTitle>
<AlertDescription>Wrong username and/or password</AlertDescription>
</Alert>
}
<form
className="mb-1 flex flex-col gap-6"
onSubmit={async e => {
e.preventDefault();
if (typeof username === "string" && password != null) {
setLoading(true);
const {defaultUrl, error, redirectUrl} = await login(username, password);
if (error) {
setError(true);
} else {
setUrl(redirectUrl ?? defaultUrl ?? '/');
}
setLoading(false);
}
}}
>
<label htmlFor="username">
<h6 color="blue-gray" className="-mb-3">
Username
</h6>
</label>
<Input
onChange={(event: any) => {
setUsername(event.target.value);
}}
id="username"
type="text"
autoComplete="username"
placeholder=""
/>
<label htmlFor="current-password">
<h6 color="blue-gray" className="-mb-3">
Password
</h6>
</label>
<Input
onChange={(event: any) => {
setPassword(event.target.value);
}}
id="current-password"
type="password"
autoComplete="current-password"
placeholder=""
/>
<div className="flex justify-between items-center">
<Link color="foreground" underline="always">Forgot password?</Link>
<Button color="primary" type="submit" isLoading={loading}>
{loading ? "" : "Log in"}
</Button>
</div>
</form>
</CardBody>
</Card>
</div>
);
}
+50
View File
@@ -0,0 +1,50 @@
import {useRouteMetadata} from 'Frontend/util/routing.js';
import {useEffect} from 'react';
import ProfileMenu from "Frontend/components/ProfileMenu";
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 as="button" onClick={() => navigate('/')}>
<GameyfinLogo className="h-10 fill-foreground"/>
</NavbarBrand>
<NavbarContent justify="end">
<NavbarItem>
<ProfileMenu/>
</NavbarItem>
</NavbarContent>
</Navbar>
<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>
&copy; {(new Date()).getFullYear()}&ensp;
<Link href="https://github.com/gameyfin/gameyfin/graphs/contributors" target="_blank">
Gameyfin contributors
</Link>
</p>
</footer>
</div>
);
}
/**/
+47
View File
@@ -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>
);
}
+140
View File
@@ -0,0 +1,140 @@
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, Palette, User} from "@phosphor-icons/react";
import {Card} from "@nextui-org/react";
import {SetupEndpoint} from "Frontend/generated/endpoints";
import {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
import {useNavigate} from "react-router-dom";
import {toast} from "sonner";
function WelcomeStep() {
return (
<div className="flex flex-col size-full items-center">
<div className="flex flex-col w-1/2 min-w-[468px] gap-12 items-center">
<h4>Welcome to Gameyfin 👋</h4>
<p className="place-content-center text-justify">
Gameyfin is a cutting-edge software tailored for gamers seeking efficient management of their
video
game collections. <br/><br/> With its intuitive interface and comprehensive features, Gameyfin
simplifies the organization of game libraries. Users can effortlessly add games through manual
input
or
automated recognition, categorize them based on various criteria like genre or platform, track
in-game
progress, and share achievements with friends. <br/><br/> Notably, Gameyfin stands out for its
user-friendly
design and adaptability, offering ample customization options to meet diverse user preferences.
</p>
<h5>Let's get started!</h5>
</div>
</div>
);
}
function ThemeStep() {
return (
<ThemeSelector/>
);
}
function UserStep() {
return (
<div className="flex flex-col size-full items-center">
<div className="flex flex-col w-1/2 min-w-[468px] gap-12 items-center">
<h4>Create your account</h4>
<p className="-mt-8">This will set up the initial admin user account.</p>
<div className="mb-1 flex flex-col w-full gap-6 items-center">
<Input
label="Username"
name="username"
type="text"
/>
<Input
label="E-Mail"
name="email"
type="email"
/>
<Input
label="Password"
name="password"
type="password"
/>
<Input
label="Password (repeat)"
name="passwordRepeat"
type="password"
/>
</div>
</div>
</div>
);
}
function SettingsStep() {
return (
<div className="flex flex-col size-full items-center">
<div className="flex flex-col w-1/2 min-w-[468px] gap-12 items-center">
<h4>Settings</h4>
<p>Configure your settings</p>
</div>
</div>
);
}
function SetupView() {
const navigate = useNavigate();
return (
<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: ''}}
onSubmit={
async (values: any) => {
await SetupEndpoint.registerSuperAdmin({
username: values.username,
password: values.password,
email: values.email
});
toast.success("Setup finished", {description: "Have fun with Gameyfin!"});
navigate('/login');
}
}
>
<WizardStep icon={<HandWaving/>}>
<WelcomeStep/>
</WizardStep>
<WizardStep icon={<Palette/>}>
<ThemeStep/>
</WizardStep>
<WizardStep
validationSchema={Yup.object({
username: Yup.string()
.required('Required'),
password: Yup.string()
.min(8, 'Password must be at least 8 characters long')
.required('Required'),
email: Yup.string()
.email()
.required('Required'),
passwordRepeat: Yup.string()
.equals([Yup.ref('password')], 'Passwords do not match')
.required('Required')
})}
icon={<User/>}
>
<UserStep/>
</WizardStep>
<WizardStep icon={<GearFine/>}>
<SettingsStep/>
</WizardStep>
</Wizard>
</Card>
</div>
);
}
export default SetupView;
+44
View File
@@ -0,0 +1,44 @@
import {Link} from "react-router-dom";
import {Button} from "@nextui-org/react";
import {toast} from "sonner";
import {SystemEndpoint} from "Frontend/generated/endpoints.js";
export default function TestView() {
return (
<div className="grow justify-center mt-12">
<div className="flex flex-col items-center gap-6">
<Link to="/setup">Setup</Link>
<div className="flex flex-row gap-4">
<Button onPress={
() => toast("Normal", {
description: "Description",
action: {
label: "OK",
onClick: () => {
},
}
})}>Toast (Normal)</Button>
<Button onPress={
() => toast.success("Success", {
description: "Description",
action: {
label: "OK",
onClick: () => {
},
}
})}>Toast (Success)</Button>
<Button onPress={
() => toast.error("Error", {
description: "Description",
action: {
label: "OK",
onClick: () => {
},
}
})}>Toast (Error)</Button>
</div>
<Button onPress={() => SystemEndpoint.restart()}>Restart</Button>
</div>
</div>
);
}