Fix Multi-Step-Wizard

Implement generic Wizard component for future use in Gameyfin
This commit is contained in:
grimsi
2024-03-16 14:48:06 +01:00
parent 0b696e4766
commit 4ad0914b17
6 changed files with 185 additions and 150 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="UI debug" type="JavascriptDebugType" engineId="37cae5b9-e8b2-4949-9172-aafa37fbc09c" uri="http://localhost:8080" useFirstLineBreakpoints="true"> <configuration default="false" name="UI debug" type="JavascriptDebugType" uri="http://localhost:8080" useFirstLineBreakpoints="true">
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>
-17
View File
@@ -31,20 +31,3 @@ const Input = ({label, ...props}) => {
} }
export default Input; 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
/>
*/
+87
View File
@@ -0,0 +1,87 @@
import React, {useState} from "react";
import {Form, Formik, FormikBag, FormikHelpers} from "formik";
import {Button, Spinner, Step, Stepper} from "@material-tailwind/react";
import {ArrowLeft, ArrowRight, Check} from "@phosphor-icons/react";
const Wizard = ({children, initialValues, onSubmit}: { children: any, initialValues: any, onSubmit: any }) => {
const [stepNumber, setStepNumber] = useState(0);
const steps = React.Children.toArray(children);
const [snapshot, setSnapshot] = useState(initialValues);
const step = steps[stepNumber];
const totalSteps = steps.length;
const isFirstStep = stepNumber === 0;
const isLastStep = stepNumber === totalSteps - 1;
const next = (values: any) => {
setSnapshot(values);
setStepNumber(Math.min(stepNumber + 1, totalSteps - 1));
};
const previous = (values: any) => {
setSnapshot(values);
setStepNumber(Math.max(stepNumber - 1, 0));
};
const handleSubmit = async (values: any, bag: FormikBag<any, any> | FormikHelpers<any>) => {
/*// @ts-ignore*/
if (step.props.onSubmit) {
/*// @ts-ignore*/
await step.props.onSubmit(values, bag);
}
if (isLastStep) {
return onSubmit(values, bag);
} else {
await bag.setTouched({});
next(values);
}
};
return (
<Formik
initialValues={snapshot}
onSubmit={handleSubmit}
isInitialValid={false}
/*// @ts-ignore*/
validationSchema={step.props.validationSchema}
>
{formik => (
<Form className="flex flex-col grow">
<div className="w-full mb-8">
<Stepper
activeStep={stepNumber}
>
{steps.map((child, index) => (
<Step key={index}>
{/*// @ts-ignore*/}
{child.props.icon}
</Step>
))}
</Stepper>
</div>
<div className="flex grow">
{step}
</div>
<div className="bottom-0 w-full">
<div className="flex justify-between">
<Button onClick={() => previous(formik.values)} disabled={isFirstStep}
className="rounded-full">
<ArrowLeft/>
</Button>
<Button disabled={formik.isSubmitting}
className="rounded-full"
type="submit"
>
{formik.isSubmitting ?
<Spinner className="h-5 w-5"/> : isLastStep ? <Check/> : <ArrowRight/>
}
</Button>
</div>
</div>
</Form>
)}
</Formik>
);
};
export default Wizard;
@@ -0,0 +1,3 @@
const WizardStep = ({children}: { children: any }) => children;
export default WizardStep;
+50 -88
View File
@@ -1,24 +1,13 @@
import {Button, Card, Spinner, Step, Stepper, Typography} from "@material-tailwind/react"; import React from 'react';
import {useState} from "react"; import {useFormikContext} from 'formik';
import {ArrowLeft, ArrowRight, Check, GearFine, HandWaving, User} from "@phosphor-icons/react";
import {Form, Formik} from "formik";
import * as Yup from 'yup'; import * as Yup from 'yup';
import Wizard from "Frontend/components/wizard/Wizard";
import WizardStep from "Frontend/components/wizard/WizardStep";
import {Card, Typography} from "@material-tailwind/react";
import Input from "Frontend/components/Input"; import Input from "Frontend/components/Input";
import {GearFine, HandWaving, User} from "@phosphor-icons/react";
export default function SetupView() { const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
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/>];
const handleNext = () => !isLastStep && setActiveStep((cur) => cur + 1);
const handlePrev = () => !isFirstStep && setActiveStep((cur) => cur - 1);
const finish = () => {
alert("Setup finished");
}
function WelcomeStep() { function WelcomeStep() {
return ( return (
@@ -43,32 +32,12 @@ export default function SetupView() {
} }
function UserStep() { function UserStep() {
const formik = useFormikContext();
return ( return (
<div className="flex flex-col size-full gap-12 items-center"> <div className="flex flex-col size-full gap-12 items-center">
<Typography variant="h4">Create your account</Typography> <Typography variant="h4">Create your account</Typography>
<Typography className="-mt-8">This will set up the initial admin user account.</Typography> <Typography className="-mt-8">This will set up the initial admin user account.</Typography>
<Formik <div className="mb-1 flex flex-col w-full gap-6">
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 <Input
label="Username" label="Username"
name="username" name="username"
@@ -84,67 +53,60 @@ export default function SetupView() {
name="passwordRepeat" name="passwordRepeat"
type="password" type="password"
/> />
<Button </div>
type="submit"
disabled={!formik.isValid || formik.isSubmitting}
className="justify-center"
>
{formik.isSubmitting ?
<Spinner className="h-4 w-full"/> : "Submit"
}
</Button>
</Form>
}
</Formik>
</div> </div>
); );
} }
function SettingsStep() { function SettingsStep() {
return ( return (
<> <div className="flex flex-col items-center">
</> <Typography variant="h4">Settings</Typography>
<Typography>Configure your settings</Typography>
</div>
); );
} }
return ( const SetupView = () => (
<div className="flex h-screen"> <div className="flex h-screen">
<div className="fixed size-full bg-gradient-to-br from-gf-primary to-gf-secondary"></div> <div className="fixed size-full bg-gradient-to-br from-gf-primary to-gf-secondary"></div>
<Card className="w-3/4 h-3/4 min-w-[500px] 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"> <Wizard
<Stepper initialValues={{username: '', password: '', passwordRepeat: ''}}
activeStep={activeStep} onSubmit={async (values: any) =>
isLastStep={(value) => setIsLastStep(value)} sleep(300).then(() => alert(JSON.stringify(values, null, 2)))
isFirstStep={(value) => setIsFirstStep(value)} }
> >
<Step onClick={() => setActiveStep(0)}> <WizardStep
<HandWaving/> // @ts-ignore
</Step> icon={<HandWaving/>}>
<Step onClick={() => setActiveStep(1)}> <WelcomeStep/>
<User/> </WizardStep>
</Step> <WizardStep
<Step onClick={() => setActiveStep(2)}> // @ts-ignore
<GearFine/> validationSchema={Yup.object({
</Step> username: Yup.string()
</Stepper> .required('Required'),
</div> password: Yup.string()
<div className="flex flex-grow justify-center"> .min(8, 'Password must be at least 8 characters long')
<div className="size-full px-8 min-w-[500px] md:w-1/3 md:px-0"> .required('Required'),
{steps[activeStep]} passwordRepeat: Yup.string()
</div> .equals([Yup.ref('password')], 'Passwords do not match')
</div> .required('Required')
<div className="bottom-0 w-full"> })}
<div className="flex justify-between"> icon={<User/>}
<Button onClick={handlePrev} disabled={isFirstStep} className="rounded-full"> >
<ArrowLeft/> <UserStep/>
</Button> </WizardStep>
<Button disabled={isNextDisabled} onClick={isLastStep ? finish : handleNext} <WizardStep
className="rounded-full"> //@ts-ignore
{isLoading ? <Spinner/> : isLastStep ? <Check/> : <ArrowRight/>} icon={<GearFine/>}
</Button> >
</div> <SettingsStep/>
</div> </WizardStep>
</Wizard>
</Card> </Card>
</div> </div>
); );
}
export default SetupView;
Vendored Regular → Executable
View File