Skip to content

Commit 7633757

Browse files
committed
Setup wizard to stepper
1 parent cc70925 commit 7633757

File tree

1 file changed

+127
-46
lines changed

1 file changed

+127
-46
lines changed

frontend/src/components/SetupWizard.jsx

Lines changed: 127 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,78 @@
11
import React, { useState, useEffect } from "react";
2-
import { Container, Grid2, TextField, Button, CircularProgress, Typography } from "@mui/material";
2+
import { Container, TextField, Button, CircularProgress, Typography, Box, styled } from "@mui/material";
3+
import Stepper from '@mui/material/Stepper';
4+
import Step from '@mui/material/Step';
5+
import StepLabel from '@mui/material/StepLabel';
6+
import StepConnector, { stepConnectorClasses } from '@mui/material/StepConnector';
37
import { useNavigate } from "react-router-dom";
48
import api from '../utils/api';
59
import * as Yup from 'yup';
610
import { useFormik } from 'formik';
7-
import { REDIRECT_REASONS } from './constants/Constants';
811
import { showToast } from '../utils/toastUtils';
12+
import Check from '@mui/icons-material/Check';
13+
import SettingsIcon from '@mui/icons-material/Settings';
14+
import GroupAddIcon from '@mui/icons-material/GroupAdd';
15+
import TitleIcon from '@mui/icons-material/Title';
16+
17+
// Custom connector styling
18+
const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
19+
[`&.${stepConnectorClasses.alternativeLabel}`]: {
20+
top: 22,
21+
},
22+
[`&.${stepConnectorClasses.active}`]: {
23+
[`& .${stepConnectorClasses.line}`]: {
24+
backgroundImage: 'linear-gradient(95deg, #2196f3 0%, #1976d2 100%)',
25+
},
26+
},
27+
[`&.${stepConnectorClasses.completed}`]: {
28+
[`& .${stepConnectorClasses.line}`]: {
29+
backgroundImage: 'linear-gradient(95deg, #2196f3 0%, #1976d2 100%)',
30+
},
31+
},
32+
[`& .${stepConnectorClasses.line}`]: {
33+
height: 3,
34+
border: 0,
35+
backgroundColor: theme.palette.mode === 'dark' ? theme.palette.grey[800] : '#eaeaf0',
36+
borderRadius: 1,
37+
},
38+
}));
39+
40+
// Custom step icon styling
41+
const ColorlibStepIconRoot = styled('div')(({ theme, ownerState }) => ({
42+
backgroundColor: theme.palette.mode === 'dark' ? theme.palette.grey[700] : '#ccc',
43+
zIndex: 1,
44+
color: '#fff',
45+
width: 50,
46+
height: 50,
47+
display: 'flex',
48+
borderRadius: '50%',
49+
justifyContent: 'center',
50+
alignItems: 'center',
51+
...(ownerState.active && {
52+
backgroundImage: 'linear-gradient(136deg, #2196f3 0%, #1976d2 100%)',
53+
boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)',
54+
}),
55+
...(ownerState.completed && {
56+
backgroundImage: 'linear-gradient(136deg, #2196f3 0%, #1976d2 100%)',
57+
}),
58+
}));
59+
60+
// Custom step icon component
61+
function ColorlibStepIcon(props) {
62+
const { active, completed, className } = props;
63+
64+
const icons = {
65+
1: <SettingsIcon />,
66+
2: <GroupAddIcon />,
67+
3: <TitleIcon />,
68+
};
69+
70+
return (
71+
<ColorlibStepIconRoot ownerState={{ completed, active }} className={className}>
72+
{completed ? <Check /> : icons[String(props.icon)]}
73+
</ColorlibStepIconRoot>
74+
);
75+
}
976

1077
function SetupWizard() {
1178
const [currentStep, setCurrentStep] = useState(0);
@@ -22,7 +89,6 @@ function SetupWizard() {
2289

2390
setSetupStatus(statusData);
2491

25-
// Update currentStep based on setup status
2692
if (!statusData.database_configured) {
2793
setCurrentStep(0);
2894
} else if (!statusData.superuser_exists) {
@@ -31,9 +97,7 @@ function SetupWizard() {
3197
setCurrentStep(2);
3298
}
3399
} catch (error) {
34-
// Handle the case where the status code is 503
35100
if (error.response && error.response.status === 503) {
36-
// Setup is incomplete, determine which step to show
37101
const statusData = error.response.data;
38102
setSetupStatus(statusData);
39103
if (!statusData.database_configured) {
@@ -44,11 +108,9 @@ function SetupWizard() {
44108
setCurrentStep(2);
45109
}
46110
} else {
47-
// Handle other errors or show a generic error message
48111
showToast('Failed to fetch setup status.', 'error');
49112
}
50113
} finally {
51-
// Ensure loading is stopped and status is marked as fetched
52114
setIsLoading(false);
53115
setStatusFetched(true);
54116
}
@@ -63,11 +125,11 @@ function SetupWizard() {
63125
title: "Database Configuration",
64126
description: "Enter your database connection details.",
65127
fields: [
66-
{ id: "db_host", label: "Database Host", type: "text", required: true },
67-
{ id: "db_port", label: "Database Port", type: "number", required: true },
68-
{ id: "db_name", label: "Database Name", type: "text", required: true },
69-
{ id: "db_user", label: "Database User", type: "text", required: true },
70-
{ id: "db_password", label: "Database Password", type: "password", required: true }
128+
{ id: "db_host", label: "Database Host", type: "text", required: true },
129+
{ id: "db_port", label: "Database Port", type: "number", required: true },
130+
{ id: "db_name", label: "Database Name", type: "text", required: true },
131+
{ id: "db_user", label: "Database User", type: "text", required: true },
132+
{ id: "db_password", label: "Database Password", type: "password", required: true }
71133
],
72134
validationSchema: Yup.object({
73135
db_host: Yup.string().required("Required"),
@@ -82,10 +144,10 @@ function SetupWizard() {
82144
title: "Admin User",
83145
description: "Create an admin user for the application (if one doesn't exist).",
84146
fields: [
85-
{ id: "admin_username", label: "Admin Username", type: "text", required: true },
86-
{ id: "admin_email", label: "Admin Email", type: "email", required: true },
87-
{ id: "admin_password", label: "Admin Password", type: "password", required: true },
88-
{ id: "admin_password2", label: "Confirm Password", type: "password", required: true }
147+
{ id: "admin_username", label: "Admin Username", type: "text", required: true },
148+
{ id: "admin_email", label: "Admin Email", type: "email", required: true },
149+
{ id: "admin_password", label: "Admin Password", type: "password", required: true },
150+
{ id: "admin_password2", label: "Confirm Password", type: "password", required: true }
89151
],
90152
validationSchema: Yup.object({
91153
admin_username: Yup.string().required("Required"),
@@ -202,27 +264,42 @@ function SetupWizard() {
202264

203265
if (!statusFetched) {
204266
return (
205-
<div className="text-center mt-5">
267+
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
206268
<CircularProgress />
207-
</div>
269+
</Box>
208270
);
209271
}
210272

211273
if (isLoading) {
212-
return <CircularProgress />;
274+
return (
275+
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
276+
<CircularProgress />
277+
</Box>
278+
);
213279
}
214280

215281
return (
216282
<Container>
217-
<Grid2 container justifyContent="center">
218-
<Grid2 item xs={12} md={8} lg={6}>
283+
<Box sx={{ width: '100%', mt: 4 }}>
284+
<Stepper alternativeLabel activeStep={currentStep} connector={<ColorlibConnector />}>
285+
{steps.map((step) => (
286+
<Step key={step.id}>
287+
<StepLabel StepIconComponent={ColorlibStepIcon}>{step.title}</StepLabel>
288+
</Step>
289+
))}
290+
</Stepper>
291+
292+
<Box sx={{ mt: 4, mb: 2 }}>
219293
<Typography variant="h4" component="h2" gutterBottom>
220294
{steps[currentStep].title}
221295
</Typography>
222296
<Typography variant="body1" gutterBottom>
223297
{steps[currentStep].description}
224298
</Typography>
225-
<form onSubmit={formik.handleSubmit} noValidate>
299+
</Box>
300+
301+
<form onSubmit={formik.handleSubmit} noValidate>
302+
<Box sx={{ mb: 4 }}>
226303
{steps[currentStep].fields.map(field => (
227304
<TextField
228305
key={field.id}
@@ -236,36 +313,40 @@ function SetupWizard() {
236313
fullWidth
237314
margin="normal"
238315
error={formik.errors[field.id] && formik.touched[field.id]}
239-
helperText={formik.errors[field.id]}
240-
required={field.required} // Add this line
316+
helperText={formik.touched[field.id] && formik.errors[field.id]}
317+
required={field.required}
241318
/>
242319
))}
243-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
244-
{currentStep > 0 && (
245-
<Button
246-
type="button"
247-
onClick={() => setCurrentStep(step => step - 1)}
248-
style={{ marginRight: '10px' }}
249-
variant="outlined"
250-
>
251-
Back
252-
</Button>
253-
)}
320+
</Box>
321+
322+
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 3 }}>
323+
{currentStep > 0 && (
254324
<Button
255-
type="submit"
256-
variant="contained"
257-
color="primary"
258-
disabled={isLoading}
259-
startIcon={isLoading ? <CircularProgress size="1rem" /> : null}
325+
variant="outlined"
326+
onClick={() => setCurrentStep(step => step - 1)}
327+
sx={{ mr: 1 }}
260328
>
261-
{isLoading ? <CircularProgress size="1rem" /> : (currentStep < steps.length - 1 ? "Next" : "Finish")}
329+
Back
262330
</Button>
263-
</div>
264-
</form>
265-
</Grid2>
266-
</Grid2>
331+
)}
332+
<Button
333+
type="submit"
334+
variant="contained"
335+
color="primary"
336+
disabled={isLoading}
337+
sx={{ ml: 'auto' }}
338+
>
339+
{isLoading ? (
340+
<CircularProgress size={24} />
341+
) : (
342+
currentStep < steps.length - 1 ? "Next" : "Finish"
343+
)}
344+
</Button>
345+
</Box>
346+
</form>
347+
</Box>
267348
</Container>
268349
);
269350
}
270351

271-
export default SetupWizard;
352+
export default SetupWizard;

0 commit comments

Comments
 (0)