Please follow the steps to create your account
export type ProgressSubStep = {
id: number,
name: string,
current: boolean,
completed: boolean,
icon: ReactElement
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
type Props = {
subSteps: ProgressSubStep[],
setSubSteps: Dispatch<SetStateAction<ProgressSubStep[]>>,
children: ReactElement,
}
export const ProgressSubSteps = ({ subSteps, setSubSteps, children }: Props) => {
const currentSubStep = subSteps.find(step => step.current) || subSteps[0];
{/* Allowing a user to jump back to a step only if has been completed already */}
const onSubStepClick = (subStep: ProgressSubStep) => {
return (subStep.id === 1 || subStep.completed) ? handleJumpToSubStep(setSubSteps, subSteps, subStep.id) : null
}
return (
<>
{subSteps.map((subStep, index) => {
return (
<div key={index} className='bg-white rounded-lg p-6 shadow-lg border-[1px] border-asteroid-300/30'>
<div onClick={() => onSubStepClick(subStep)} className='flex justify-between cursor-pointer'>
<div className='font-bold text-base flex items-center gap-2'>
{subStep.icon}
{subStep.name}
</div>
<div>
<CheckCircleIcon className={classNames(subStep.completed ? 'text-green-500' : 'text-gray-300', 'h-7 w-7')} />
</div>
</div>
{/*
CSS transitions don't work with h-auto, so this trick with using grid 0fr/1fr is necessary.
(until new CSS "interpolate-size: 'allow-keywords'", which solves this exact scenario, is more widely supported)
*/}
<div className={classNames('grid transition-[grid-template-rows]', subStep.id === currentSubStep.id ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]')}>
<div className='overflow-hidden'>
<div className='mt-4 h-auto'>{subStep.id === currentSubStep.id ? children : null}</div>
</div>
</div>
</div>
)
})}
</>
)
}
export const handleJumpToSubStep = (setSubSteps: Dispatch<SetStateAction<ProgressSubStep[]>>, subSteps: ProgressSubStep[], newSubStepId: number) => {
let clonedSteps = cloneDeep(subSteps);
clonedSteps = clonedSteps.map(step => ({
...step,
current: step.id === newSubStepId
}))
setSubSteps(clonedSteps);
}
export const handleCompleteSubStep = (setSubSteps: Dispatch<SetStateAction<ProgressSubStep[]>>, subSteps: ProgressSubStep[], subStepId: number) => {
let clonedSteps = cloneDeep(subSteps);
clonedSteps = clonedSteps.map(step => ({
...step,
completed: step.id === subStepId ? true : step.completed
}))
handleJumpToSubStep(setSubSteps, clonedSteps, subStepId + 1)
}
export type AccountInfo = {
username: string,
fullName: string
}
export type SupportedPlatform = "" | "PC" | "Playstation" | "Nintendo";
export type PlatformInfo = {
platform: SupportedPlatform,
favouriteGames?: string[]
}
export type ContactInfo = {
email: string
}
export type SignUpInfo = AccountInfo & PlatformInfo & ContactInfo;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
type Props = {
handleGoToNextStep: (signUpInfo: SignUpInfo) => void,
initialValues: SignUpInfo
}
export const StepOneAccountSetup = ({ handleGoToNextStep, initialValues }: Props) => {
const [signUpInfo, setSignUpInfo] = useState<SignUpInfo>(initialValues);
const initialSubSteps: ProgressSubStep[] = [{
id: 1,
name: 'Tell us about yourself',
current: true,
completed: Boolean(signUpInfo.username) && Boolean(signUpInfo.fullName),
icon: <UserIcon className='h-8 w-8 text-indigo-500' />
}, {
id: 2,
name: 'About your gaming experience',
current: false,
completed: Boolean(signUpInfo.platform),
icon: <TvIcon className='h-8 w-8 text-indigo-500' />
}, {
id: 3,
name: 'Contact information',
current: false,
completed: Boolean(signUpInfo.email),
icon: <AtSymbolIcon className='h-8 w-8 text-indigo-500' />
}];
const [subSteps, setSubSteps] = useState<ProgressSubStep[]>(initialSubSteps);
const currentSubStep = subSteps.find(step => step.current) || subSteps[0];
const getSubStepContent = () => {
switch (currentSubStep.id) {
case 1:
return (
<StepOneSubStepOne
initialValues={{
username: signUpInfo.username,
fullName: signUpInfo.fullName
}}
handleSubmit={(accountInfo: AccountInfo) => {
handleCompleteSubStep(setSubSteps, subSteps, 1);
setSignUpInfo({
...signUpInfo,
...accountInfo
})
}}
/>
);
case 2:
return (
<StepOneSubStepTwo
initialValues={{
platform: signUpInfo.platform
}}
handleSubmit={(platformInfo: PlatformInfo) => {
handleCompleteSubStep(setSubSteps, subSteps, 2);
setSignUpInfo({
...signUpInfo,
...platformInfo
})
}}
/>
)
case 3:
return (
<StepOneSubStepThree
initialValues={{
email: signUpInfo.email
}}
handleSubmit={(contactInfo: ContactInfo) => {
const updatedSignUpInfo = {
...signUpInfo,
...contactInfo
};
handleGoToNextStep(updatedSignUpInfo);
}}
/>
)
default:
return <></>
}
}
return (
<ProgressSubSteps
allowOverflowForSteps={[2]}
setSubSteps={setSubSteps}
subSteps={subSteps}
>
{getSubStepContent()}
</ProgressSubSteps>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
type Props = {
handleSubmit: ({ username, fullName }: AccountInfo) => void,
initialValues: AccountInfo
}
export const StepOneSubStepOne = ({ handleSubmit, initialValues }: Props) => {
/* This object is defining how we want to restrict each field,
and the associated error message if a rule is not respected */
const validationSchemaYup = Yup.object().shape({
username: Yup.string()
.required('Username is mandatory),
fullName: Yup.string()
.required('Full name is mandatory),
});
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchemaYup}
onSubmit={handleSubmit}>
{({ errors, touched }) => (
<Form>
<div>
<p>Username</p>
<Field className="pl-2 block w-full rounded-md border-0 py-1.5 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" id="username" name="username" />
{errors.username && touched.username ? (
<div className='mt-2 lg:text-base text-sm text-red-600'>{errors.username}</div>
) : null}
<p>Full name</p>
<Field className="pl-2 block w-full rounded-md border-0 py-1.5 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" id="fullName" name="fullName" />
{errors.fullName && touched.fullName ? (
<div className='mt-2 lg:text-base text-sm text-red-600'>{errors.fullName}</div>
) : null}
</div>
<div className='flex justify-end'>
<button
disabled={Object.keys(errors).length !== 0}
type="submit"
className="h-10 flex justify-center my-4 w-40 self-center rounded-md bg-slate-700 px-3 text-sm font-semibold text-white shadow-sm hover:bg-slate-600 disabled:cursor-not-allowed items-center"
>
<div className="font-semibold lg:text-lg text-base !text-indigo-50">Continue</div>
</button>
</div>
</Form>
)}
</Formik>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
export const ProgressShell = () => {
const defaultSteps: ProgressStep[] = [{
id: 1,
name: 'Account details',
current: true,
completed: true
}, {
id: 2,
name: 'Pick your games',
current: false,
completed: true
}, {
id: 3,
name: 'Complete',
current: false,
completed: true
}]
const [steps, setSteps] = useState<ProgressStep[]>(defaultSteps);
const [signUpInfo, setSignUpInfo] = useState<SignUpInfo>({
fullName: '',
username: '',
platform: '',
email: '',
favouriteGames: []
});
const currentStep = steps.find(step => step.current) || steps[0];
const stepCount = steps.length;
const generateJumpToStep = (stepsToUpdate: ProgressStep[], newStepId: number): ProgressStep[] => {
const clonedSteps = cloneDeep(stepsToUpdate);
const newSteps = clonedSteps.map(step => ({
...step,
current: step.id === newStepId
}));
return newSteps;
}
const handleChangeStep = (stepsToUpdate: ProgressStep[], newStepId: number) => {
// Jumping back to a previous step is allowed
setSteps(generateJumpToStep(stepsToUpdate, newStepId));
}
const manuallyChangeStep = (stepsToUpdate: ProgressStep[], newStepId: number) => {
if (currentStep.id > newStepId) {
handleChangeStep(stepsToUpdate, newStepId);
}
}
const getStepContent = () => {
switch (currentStep.id) {
case 1:
return (
<StepOneAccountSetup
initialValues={signUpInfo}
handleGoToNextStep={(signUpInfo) => {
setSignUpInfo(signUpInfo);
handleChangeStep(steps, 2);
}}
/>
)
case 2:
return (
<StepTwoGameSelection
initialValues={signUpInfo.favouriteGames || []}
platform={signUpInfo.platform}
handleGoToNextStep={(gameSelection) => {
let clonedInfo = cloneDeep(signUpInfo);
clonedInfo.favouriteGames = gameSelection;
setSignUpInfo(clonedInfo);
handleChangeStep(steps, 3);
}}
/>
)
case 3:
return (
<StepThreeTwoRecap
initialValues={signUpInfo}
/>
)
default:
return <></>
}
}
return (
<div className='my-10'>
<div className='flex justify-between flex-col lg:flex-row items-center'>
<div className='mb-6 lg:md-0'>
<h6 className={'text-xl md:text-2xl text-gray-600'}>Welcome to Mist, the new gaming platform!</h6>
<p className={'text-lg text-gray-500'}>Please follow the steps to create your account</p>
</div>
{/* Progress bar */}
<div className='relative flex items-center'>
<div className='font-semibold mr-4 opacity-60'>
Step {currentStep.id} of {stepCount}
</div>
<div className='flex items-start justify-between mr-4 md:mr-8'>
{steps.map((step, index) => {
return (
<div key={index} onClick={() => manuallyChangeStep(steps, step.id)} className='relative flex items-center cursor-pointer'>
{/* Pale blue circle set as background, used to display the light blue outline of the current step */}
<div className='z-10 absolute h-8 w-8 bg-blue-300 rounded-full'>
{/* Aligning the label with the step's dot */}
<div className={classNames(!step.current && 'opacity-50', 'font-semibold text-xs text-center w-24 flex items-center justify-center mt-10 left-0 -translate-x-7')}>
{step.name}
</div>
</div>
<div className={classNames(step.current ? 'm-1 h-6 w-6' : 'h-8 w-8', 'z-20 bg-gray-200 rounded-full flex items-center justify-center transition-all')}>
<div className={classNames(currentStep.id <= step.id ? 'h-0 w-0' : 'h-6 w-6', 'bg-blue-500 rounded-full transition-all')}></div>
</div>
{stepCount !== index + 1 ? <div className='h-1 w-20 bg-gray-200'></div> : null}
</div>
)
})}
</div>
</div>
</div>
<div className='flex flex-col space-y-4'>
{getStepContent()}
</div>
</div>
)}