Implemented admin account setup step in UI

This commit is contained in:
grimsi
2024-03-10 13:55:54 +01:00
parent 647ac97309
commit 0b696e4766
6 changed files with 240 additions and 37 deletions
+50
View File
@@ -0,0 +1,50 @@
import {useField} from "formik";
import {Input as MaterialInput, Typography} from "@material-tailwind/react";
import {XCircle} from "@phosphor-icons/react";
// @ts-ignore
const Input = ({label, ...props}) => {
// @ts-ignore
const [field, meta] = useField(props);
return (
<>
<MaterialInput
{...props}
{...field}
label={label}
error={meta.touched && !!meta.error}
success={meta.touched && !meta.error}
crossOrigin=""
/>
{(meta.touched && !!meta.error) ?
<Typography
variant="small"
color="red"
className="ml-3 -mt-5 flex flex-row items-center gap-1"
>
<XCircle weight="fill" size={14}/> {meta.error}
</Typography> : <></>
}
</>
);
}
export default Input;
/*
<Input
onChange={(event) => {
setUsername(event.target.value);
}}
id="username"
type="text"
autoComplete="username"
placeholder=""
size="lg"
className=" !border-t-blue-gray-200 focus:!border-t-gray-900"
labelProps={{
className: "before:content-none after:content-none",
}}
crossOrigin="" //TODO: see https://github.com/creativetimofficial/material-tailwind/issues/427
/>
*/
+4 -6
View File
@@ -8,18 +8,16 @@ import SetupView from "Frontend/views/SetupView";
export const routes = protectRoutes([
{
element: <MainLayout/>,
handle: {title: 'Main', requiresLogin: true},
handle: {requiresLogin: true},
children: [
{path: '/', element: <TestView/>, handle: {title: 'Gameyfin', requiresLogin: true}},
{path: '/', element: <TestView/>, handle: {title: 'Gameyfin - Test'}},
],
},
{
path: '/login',
element: <LoginView/>
path: '/login', element: <LoginView/>, handle: {requiresLogin: false}
},
{
path: '/setup',
element: <SetupView/>
path: '/setup', element: <SetupView/>, handle: {requiresLogin: false}
}
]) as RouteObject[];
+1 -8
View File
@@ -1,22 +1,15 @@
import {useAuth} from 'Frontend/util/auth.js';
import {useRouteMetadata} from 'Frontend/util/routing.js';
import {useEffect} from 'react';
import {Navbar} from "@material-tailwind/react";
import ProfileMenu from "Frontend/components/ProfileMenu";
import {Outlet} from "react-router-dom";
const navLinkClasses = ({isActive}: any) => {
return `block rounded-m p-s ${isActive ? 'bg-primary-10 text-primary' : 'text-body'}`;
};
export default function MainLayout() {
const currentTitle = useRouteMetadata()?.title ?? 'My App';
const currentTitle = `Gameyfin - ${useRouteMetadata()?.title}` ?? 'Gameyfin';
useEffect(() => {
document.title = currentTitle;
}, [currentTitle]);
const {state, logout} = useAuth();
return (
<>
<Navbar className="sticky top-0 z-10 h-max max-w-full rounded-none px-4 py-2">
+70 -10
View File
@@ -1,12 +1,16 @@
import {Button, Card, Spinner, Step, Stepper, Typography} from "@material-tailwind/react";
import {useState} from "react";
import {ArrowLeft, ArrowRight, Check, GearFine, HandWaving, User} from "@phosphor-icons/react";
import {Form, Formik} from "formik";
import * as Yup from 'yup';
import Input from "Frontend/components/Input";
export default function SetupView() {
const [activeStep, setActiveStep] = useState(0);
const [isLastStep, setIsLastStep] = useState(false);
const [isFirstStep, setIsFirstStep] = useState(false);
const [isLoading, setLoading] = useState(false);
const [isNextDisabled, setIsNextDisabled] = useState(false);
const steps = [<WelcomeStep/>, <UserStep/>, <SettingsStep/>];
@@ -18,19 +22,21 @@ export default function SetupView() {
function WelcomeStep() {
return (
<div className="flex flex-col gap-12 w-full items-center">
<div className="flex flex-col size-full gap-12 items-center">
<Typography variant="h4">Welcome to Gameyfin 👋</Typography>
<text className="w-1/3 min-w-[500px] text-justify">
Gameyfin is a cutting-edge software tailored for gamers seeking efficient management of their video
<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
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.
</text>
</p>
<Typography variant="h5">Let's get started!</Typography>
</div>
);
@@ -38,8 +44,59 @@ export default function SetupView() {
function UserStep() {
return (
<>
</>
<div className="flex flex-col size-full gap-12 items-center">
<Typography variant="h4">Create your account</Typography>
<Typography className="-mt-8">This will set up the initial admin user account.</Typography>
<Formik
validateOnMount
initialValues={{username: '', password: '', passwordRepeat: ''}}
validationSchema={Yup.object({
username: Yup.string()
.required('Required'),
password: Yup.string()
.min(8, 'Password must be at least 8 characters long')
.required('Required'),
passwordRepeat: Yup.string()
.equals([Yup.ref('password')], 'Passwords do not match')
.required('Required')
})}
onSubmit={(values, {setSubmitting}) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{formik =>
<Form className="mb-1 flex flex-col w-full gap-6">
<Input
label="Username"
name="username"
type="text"
/>
<Input
label="Password"
name="password"
type="password"
/>
<Input
label="Password (repeat)"
name="passwordRepeat"
type="password"
/>
<Button
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
className="justify-center"
>
{formik.isSubmitting ?
<Spinner className="h-4 w-full"/> : "Submit"
}
</Button>
</Form>
}
</Formik>
</div>
);
}
@@ -53,7 +110,7 @@ export default function SetupView() {
return (
<div className="flex h-screen">
<div className="fixed size-full bg-gradient-to-br from-gf-primary to-gf-secondary"></div>
<Card className="w-3/4 h-3/4 m-auto p-8" shadow={true}>
<Card className="w-3/4 h-3/4 min-w-[500px] m-auto p-8" shadow={true}>
<div className="w-full mb-8">
<Stepper
activeStep={activeStep}
@@ -72,14 +129,17 @@ export default function SetupView() {
</Stepper>
</div>
<div className="flex flex-grow justify-center">
{steps[activeStep]}
<div className="size-full px-8 min-w-[500px] md:w-1/3 md:px-0">
{steps[activeStep]}
</div>
</div>
<div className="bottom-0 w-full">
<div className="flex justify-between">
<Button onClick={handlePrev} disabled={isFirstStep} className="rounded-full">
<ArrowLeft/>
</Button>
<Button onClick={isLastStep ? finish : handleNext} className="rounded-full">
<Button disabled={isNextDisabled} onClick={isLastStep ? finish : handleNext}
className="rounded-full">
{isLoading ? <Spinner/> : isLastStep ? <Check/> : <ArrowRight/>}
</Button>
</div>
+108 -10
View File
@@ -31,10 +31,12 @@
"@vaadin/router": "1.7.5",
"classnames": "^2.3.2",
"construct-style-sheets-polyfill": "3.1.0",
"formik": "^2.4.5",
"lit": "3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.2"
"react-router-dom": "^6.4.2",
"yup": "^1.4.0"
},
"devDependencies": {
"@lit-labs/react": "^1.1.0",
@@ -3458,6 +3460,15 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
@@ -3486,14 +3497,12 @@
"node_modules/@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
"devOptional": true
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
},
"node_modules/@types/react": {
"version": "18.2.42",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.42.tgz",
"integrity": "sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==",
"devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -3521,8 +3530,7 @@
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"devOptional": true
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
@@ -5705,8 +5713,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/dash-ast": {
"version": "1.0.0",
@@ -6247,6 +6254,38 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/formik": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz",
"integrity": "sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==",
"funding": [
{
"type": "individual",
"url": "https://opencollective.com/formik"
}
],
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.1",
"deepmerge": "^2.1.1",
"hoist-non-react-statics": "^3.3.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"react-fast-compare": "^2.0.1",
"tiny-warning": "^1.0.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/formik/node_modules/deepmerge": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -6651,6 +6690,14 @@
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-9.2.2.tgz",
"integrity": "sha512-OMEdFCaG626ES1JEcKAvJTpxAOMuchy0XuAplmnOs0Yu7NMd2RMfTLFQ2fCJOxo3ubSdm/RVQwKAWC+5HYThnw=="
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -7481,8 +7528,12 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@@ -8485,6 +8536,11 @@
"react-is": "^16.13.1"
}
},
"node_modules/property-expr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
@@ -8594,6 +8650,11 @@
"react": "^18.2.0"
}
},
"node_modules/react-fast-compare": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -9788,12 +9849,22 @@
"real-require": "^0.2.0"
}
},
"node_modules/tiny-case": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"dev": true
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -9815,6 +9886,11 @@
"node": ">=8.0"
}
},
"node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
},
"node_modules/tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
@@ -11258,6 +11334,28 @@
"node": ">=12"
}
},
"node_modules/yup": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz",
"integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==",
"dependencies": {
"property-expr": "^2.0.5",
"tiny-case": "^1.0.3",
"toposort": "^2.0.2",
"type-fest": "^2.19.0"
}
},
"node_modules/yup/node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zstddec": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz",
+7 -3
View File
@@ -26,10 +26,12 @@
"@vaadin/router": "1.7.5",
"classnames": "^2.3.2",
"construct-style-sheets-polyfill": "3.1.0",
"formik": "^2.4.5",
"lit": "3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.2"
"react-router-dom": "^6.4.2",
"yup": "^1.4.0"
},
"devDependencies": {
"@lit-labs/react": "^1.1.0",
@@ -82,7 +84,9 @@
"@hilla/generator-typescript-plugin-backbone": "$@hilla/generator-typescript-plugin-backbone",
"@hilla/generator-typescript-cli": "$@hilla/generator-typescript-cli",
"@material-tailwind/react": "$@material-tailwind/react",
"@phosphor-icons/react": "$@phosphor-icons/react"
"@phosphor-icons/react": "$@phosphor-icons/react",
"formik": "$formik",
"yup": "$yup"
},
"vaadin": {
"dependencies": {
@@ -126,6 +130,6 @@
"workbox-core": "7.0.0",
"workbox-precaching": "7.0.0"
},
"hash": "866532fc7368b3b6387e2dee804a0a2f82b8b5c94f00c0a5fa056b2bd6af0220"
"hash": "4a15d7e79e5a115970b30fcf67cfdd2edab987a6419ed93f8f80f6969d884b1d"
}
}