/
solomin.dv
/
FrontendDeveloper
Обзор
Документация
Войти
/
solomin.dv
/
FrontendDeveloper
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Auth/SignUp/index.jsx
645 строк
20 KB
Даниил Соломин
update Auth/SignUp/index.jsx
11 июн 2024, 17:53
11 июн 2024, 17:53
cc81d02
Код
Авторство
О чём код?
import * as React from 'react'; import { ArrowBackIosNew as ArrowIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, MailLock as MailIcon, Person as PersonIcon, Key as PasswordIcon, VisibilityOff as VisibilityOffIcon, Visibility as VisibilityOnIcon, } from '@mui/icons-material'; import { Grid, Box, Typography, Container, Paper, Fab, Button, TextField, MobileStepper, FormControlLabel, Checkbox, Stepper, Step, StepLabel, FormControl, FormHelperText, InputAdornment, IconButton, } from '@mui/material'; import { useEffect, useRef, useState } from 'react'; import { IMaskInput } from 'react-imask'; import { useFormik } from 'formik'; import * as yup from 'yup'; import { getAccountData, updateAccountData } from 'routes/accounts'; import { encrypt } from 'functions/crypto'; import parsePhoneNumber from 'libphonenumber-js'; import { logIn } from 'functions/cookies'; import { styled } from '@mui/material/styles'; import StepConnector, { stepConnectorClasses } from '@mui/material/StepConnector'; import showAlert from 'functions/showAlert'; import { errorMessages as e } from '../../alertMessages'; import { nameRegex, phoneNumberRegex } from '../../constants'; const TextMaskCustom = React.forwardRef((props, ref) => { const { onChange, ...other } = props; return ( <IMaskInput {...other} mask="8 (000) 000-00-00" definitions={{ '#': /[1-9]/, }} inputRef={ref} onAccept={(value) => onChange({ target: { name: props.name, value } })} overwrite /> ); }); const CustomTextField = (props) => { const { id, name, label, autoComplete, formik, validation, isShowPassword, handleClickShowPassword, } = props; return ( <TextField size="small" fullWidth name={name} sx={{ height: '68px', width: '350px' }} color={validation ? 'success' : 'primary'} label={label} type={((name === 'password' && !isShowPassword) || (name === 'confirmed_password' && !isShowPassword)) ? 'password' : 'text'} id={id} autoComplete={autoComplete} value={formik.values[name]} onChange={formik.handleChange} error={formik.touched[name] && Boolean(formik.errors[name])} helperText={formik.touched[name] && formik.errors[name]} InputProps={{ inputComponent: name === 'phone_number' ? TextMaskCustom : undefined, endAdornment: (name === 'password' || name === 'confirmed_password') && ( <InputAdornment position="end"> <IconButton aria-label="toggle password visibility" onClick={handleClickShowPassword} edge="end" > {isShowPassword ? <VisibilityOffIcon /> : <VisibilityOnIcon />} </IconButton> </InputAdornment> ), }} /> ); }; export default function SignUp({ setIsShowSignIn, setIsShowSignUp }) { const [activeStep, setActiveStep] = useState(0); const [isValidEmail, setIsValidEmail] = useState(undefined); const [isValidPersonalData, setIsValidPersonalData] = useState(undefined); const [isValidPassword, setIsValidPassword] = useState(undefined); const isEmailChange = useRef(false); const isPersonalDataChange = useRef(false); const [isShowPassword, setIsShowPassword] = useState(false); const [phoneNumber, setPhoneNumber] = useState(undefined); const [registerAccountData, setRegisterAccountData] = useState(null); const [stateEmail, setStateEmail] = useState(null); const emailErrorMessage = useRef(''); const handleNext = () => { setActiveStep((prevActiveStep) => prevActiveStep + 1); }; const handleBack = () => { setActiveStep((prevActiveStep) => prevActiveStep - 1); }; const handleClickShowPassword = () => { setIsShowPassword(!isShowPassword); }; const initialValuesEmail = { email: '', }; const initialValuesPersonalData = { last_name: '', first_name: '', middle_name: '', phone_number: '', checked: false, }; const initialValuesPassword = { password: '', confirmed_password: '', }; const validationSchemaEmail = yup.object({ email: yup .string() .email(e.ERROR_INVALID_EMAIL) .max(255, e.ERROR_MAX_EMAIL) .required(e.ERROR_REQ_EMAIL), }); const validationSchemaPersonalData = yup.object({ last_name: yup .string() .matches(nameRegex, e.ERROR_INVALID_LAST_NAME) .required(e.ERROR_REQ_LAST_NAME), first_name: yup .string() .matches(nameRegex, e.ERROR_INVALID_FIRST_NAME) .required(e.ERROR_REQ_FIRST_NAME), middle_name: yup .string() .matches(nameRegex, e.ERROR_INVALID_MIDDLE_NAME) .required(e.ERROR_REQ_MIDDLE_NAME), phone_number: yup .string() .min(11, e.ERROR_FULL_PHONE_NUMBER) .matches(phoneNumberRegex, e.ERROR_INVALID_PHONE_NUMBER) .required(e.ERROR_REQ_PHONE_NUMBER), checked: yup .bool() .oneOf([true], e.ERROR_REQ_CHECKED), }); const validationSchemaPassword = yup.object({ password: yup .string(e.ERROR_REQ_PASSWORD) .max(255, e.ERROR_MAX_PASSWORD) .min(8, e.ERROR_MIN_PASSWORD) .required(e.ERROR_REQ_PASSWORD), confirmed_password: yup .string(e.ERROR_REQ_PASSWORD_CONFIRM) .max(255, e.ERROR_MAX_PASSWORD) .min(8, e.ERROR_MIN_PASSWORD) .oneOf([yup.ref('password')], e.ERROR_PASSWORDS_DONT_MATCH) .required(e.ERROR_REQ_PASSWORD_CONFIRM), }); const handleSubmitCheckEmail = (values, { setErrors }) => { if (stateEmail === null || stateEmail !== values.email) { setStateEmail(values.email); getAccountData({ email: values.email }).then(({ status }) => { if (status === 'success') { setIsValidEmail(true); setRegisterAccountData({ email: values.email }); emailErrorMessage.current = ''; isEmailChange.current = false; handleNext(); } else if (status === 'warning') { setIsValidEmail(false); emailErrorMessage.current = e.ERROR_EMAIL_ALREADY_EXIST; setErrors({ email: emailErrorMessage.current }); } else if (status === 'error') { setIsValidEmail(false); emailErrorMessage.current = e.ERROR_EMAIL_NOT_FOUND; setErrors({ email: emailErrorMessage.current }); } }); } else if (emailErrorMessage.current !== '') { setErrors({ email: emailErrorMessage.current }); } else if (isValidEmail === true && emailErrorMessage.current === '') { if (isEmailChange.current) isEmailChange.current = false; handleNext(); } }; const handleSubmitPersonalData = (values) => { if (isValidPersonalData === undefined) { const personalData = { last_name: values.last_name, first_name: values.first_name, middle_name: values.middle_name, phone_number: phoneNumber, email: stateEmail, }; setRegisterAccountData(personalData); setIsValidPersonalData(true); } isPersonalDataChange.current = false; handleNext(); }; const handleSubmitSignUp = (values) => { if (isValidPassword === undefined) { setIsValidPassword(true); const body = { last_name: registerAccountData.last_name, first_name: registerAccountData.first_name, middle_name: registerAccountData.middle_name, password: values.password, phone_number: phoneNumber, email: registerAccountData.email, register_status: true, }; const encryptedBody = encrypt(JSON.stringify(body)); updateAccountData(encryptedBody) .then((response) => { if (response) { showAlert('success', 'Регистрация успешно завершена'); logIn(response, false); } else { showAlert('error', 'Возникла ошибка при регистрации'); setIsValidPassword(false); } }) .catch((error) => showAlert('error', error)); } }; const formikEmail = useFormik({ initialValues: initialValuesEmail, validationSchema: validationSchemaEmail, onSubmit: handleSubmitCheckEmail, }); const formikPersonalData = useFormik({ initialValues: initialValuesPersonalData, validationSchema: validationSchemaPersonalData, onSubmit: handleSubmitPersonalData, }); const formikFinishSignUp = useFormik({ initialValues: initialValuesPassword, validationSchema: validationSchemaPassword, onSubmit: handleSubmitSignUp, }); const handleClickGoBack = () => { setIsShowSignIn(true); setIsShowSignUp(false); }; const steps = [ { label: 'Проверка электронной почты', code: ( <form onSubmit={formikEmail.handleSubmit}> <CustomTextField id="email sign up" name="email" label="Адрес электронной почты" autoComplete="email" formik={formikEmail} validation={isValidEmail} setValidation={setIsValidEmail} isChange={isEmailChange} /> </form> ), icon: (<MailIcon />), formik: formikEmail, validation: isValidEmail, }, { label: 'Ввод данных пользователя', code: ( <form onSubmit={formikPersonalData.handleSubmit}> <CustomTextField id="last_name" name="last_name" label="Фамилия" autoComplete="last-name" formik={formikPersonalData} validation={isValidPersonalData} setValidation={setIsValidPersonalData} /> <CustomTextField id="first_name" name="first_name" label="Имя" autoComplete="first-name" formik={formikPersonalData} validation={isValidPersonalData} /> <CustomTextField id="middle_name" name="middle_name" label="Отчество" autoComplete="middle-name" formik={formikPersonalData} validation={isValidPersonalData} /> <CustomTextField id="phone_number" name="phone_number" label="Номер телефона" autoComplete="phone-number" formik={formikPersonalData} validation={isValidPersonalData} /> <FormControl error={formikPersonalData.touched.checked && Boolean(formikPersonalData.errors.checked)} variant="standard" sx={{ height: '50px', pb: 0.4, pt: 0.6 }} > <FormControlLabel sx={{ height: '20px' }} control={( <Checkbox type="checkbox" name="checked" color={isValidPersonalData ? 'success' : 'primary'} disabled={Object.values(formikPersonalData.values).includes('')} onChange={formikPersonalData.handleChange} checked={formikPersonalData.values.checked} /> )} label="Данные верны" /> {formikPersonalData.touched.checked && Boolean(formikPersonalData.errors.checked) && ( <FormHelperText> {formikPersonalData.touched.checked && formikPersonalData.errors.checked} </FormHelperText> )} </FormControl> </form> ), icon: (<PersonIcon />), formik: formikPersonalData, validation: isValidPersonalData, }, { label: 'Завершение регистрации', code: ( <form onSubmit={formikFinishSignUp.handleSubmit}> <CustomTextField id="password" name="password" label="Пароль" autoComplete="new-password" formik={formikFinishSignUp} isShowPassword={isShowPassword} handleClickShowPassword={handleClickShowPassword} /> <CustomTextField id="confirmed_password" name="confirmed_password" label="Подвердите пароль" autoComplete="confirmed-new-password" formik={formikFinishSignUp} isShowPassword={isShowPassword} handleClickShowPassword={handleClickShowPassword} /> <Button type="submit" fullWidth size="small" variant="contained" sx={{ mb: 3 }} > Зарегистрироваться </Button> </form> ), icon: (<PasswordIcon />), formik: formikFinishSignUp, validation: isValidPassword, }, ]; useEffect(() => { if (isValidEmail !== undefined && Object.values(formikEmail.errors).length === 0) { setIsValidEmail(undefined); } if (isEmailChange.current === false) { formikEmail.setTouched({}); isEmailChange.current = true; } }, [formikEmail.values, formikEmail.errors]); useEffect(() => { if (Object.values(formikEmail.errors).length > 0 && formikEmail.touched.email && isValidEmail !== false) { setIsValidEmail(false); } }, [formikEmail.errors, formikEmail.touched]); useEffect(() => { if (isValidPersonalData !== undefined && Object.values(formikPersonalData.errors).length === 0) { setIsValidPersonalData(undefined); } if (isPersonalDataChange.current === false) { formikPersonalData.setFieldValue('checked', false); formikPersonalData.setTouched({}); isPersonalDataChange.current = true; } }, [formikPersonalData.values, formikPersonalData.errors]); useEffect(() => { if (Object.values(formikPersonalData.errors).length > 0 && Object.values(formikPersonalData.touched).length > 0 && isValidPersonalData !== false) { setIsValidPersonalData(false); } }, [formikPersonalData.errors, formikPersonalData.touched]); useEffect(() => { if (isValidPassword !== undefined && Object.values(formikFinishSignUp.errors).length === 0) { setIsValidPassword(undefined); } }, [formikFinishSignUp.values, formikFinishSignUp.errors]); useEffect(() => { if (Object.values(formikFinishSignUp.errors).length > 0 && Object.values(formikFinishSignUp.touched).length > 0 && isValidPassword !== false) { setIsValidPassword(false); } }, [formikFinishSignUp.errors, formikFinishSignUp.touched]); useEffect(() => { if (Object.values(formikFinishSignUp.errors).length > 0 && Object.values(formikFinishSignUp.touched).length > 0 && isValidPassword !== false) { setIsValidPassword(false); } }, [formikFinishSignUp.errors, formikFinishSignUp.touched]); useEffect(() => { if (formikPersonalData.values.phone_number) { const newPhoneNumber = parsePhoneNumber(formikPersonalData.values.phone_number, 'RU'); if (newPhoneNumber) { setPhoneNumber(8 + newPhoneNumber.nationalNumber); } } }, [formikPersonalData.values.phone_number]); const AvatarStepIconRoot = styled('div')(({ theme, ownerState }) => ({ m: 1, left: '35%', backgroundColor: theme.palette.mode === 'dark' ? theme.palette.grey[700] : '#ccc', zIndex: 1, color: '#fff', width: 42, height: 42, display: 'flex', borderRadius: '50%', justifyContent: 'center', alignItems: 'center', ...(ownerState.active && ownerState.icon === (activeStep + 1) && steps[activeStep]?.validation === undefined && { backgroundColor: '#1969d2', boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)', }), ...(ownerState.active && ownerState.icon === (activeStep + 1) && steps[activeStep]?.validation === true && { backgroundColor: '#2e7d32', boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)', }), ...(ownerState.active && ownerState.icon === (activeStep + 1) && steps[activeStep]?.validation === false && { backgroundColor: '#d32f2f', boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)', }), ...(ownerState.completed && { backgroundColor: '#2e7d32', }), })); const connectorStepColor = () => { if (steps[activeStep]?.validation === undefined) { return 'linear-gradient(to right, #2e7d32 50%, #1969d2 50%)'; } if (steps[activeStep]?.validation === false) { return 'linear-gradient(to right, #2e7d32 50%, #d32f2f 50%)'; } return ''; }; const ColorlibConnector = styled(StepConnector)(({ theme }) => ({ [`&.${stepConnectorClasses.alternativeLabel}`]: { top: 20, }, [`&.${stepConnectorClasses.completed}`]: { [`& .${stepConnectorClasses.line}`]: { backgroundColor: '#2e7d32', }, }, [`&.${stepConnectorClasses.active}`]: { [`& .${stepConnectorClasses.line}`]: { backgroundImage: connectorStepColor(), backgroundColor: '#2e7d32', }, }, [`& .${stepConnectorClasses.line}`]: { height: 3, border: 0, backgroundColor: theme.palette.mode === 'dark' ? theme.palette.grey[800] : '#eaeaf0', borderRadius: 1, }, })); function AvatarStepIcon(props) { const { active, completed, icon } = props; const icons = { 1: steps[0].icon, 2: steps[1].icon, 3: steps[2].icon, }; return ( <AvatarStepIconRoot ownerState={{ completed, active, icon }}> {/* eslint-disable-next-line react/destructuring-assignment */} {icons[String(icon)]} </AvatarStepIconRoot> ); } return ( <Container component="main" maxWidth="xs"> <Box sx={{ marginTop: 8, display: 'flex' }}> <Paper elevation={3} sx={{ alignItems: 'center', display: 'flex', flexDirection: 'column', my: { xs: 3, md: 6 }, p: { xs: 2, md: 3 }, }} > <Grid container alignItems="center" justifyContent="space-between" > <Grid item> <Fab size="small" sx={{ right: '100%' }} onClick={handleClickGoBack} > <ArrowIcon /> </Fab> </Grid> <Grid item xs> <Stepper sx={{ mr: 2, pr: 2, mb: 1 }} activeStep={activeStep} alternativeLabel connector={<ColorlibConnector />} > {steps.map((item) => ( <Step key={item.label}> <StepLabel StepIconComponent={AvatarStepIcon} /> </Step> ))} </Stepper> </Grid> </Grid> <Typography component="h1" variant="h5" sx={{ mt: 1, mb: 0.5 }}> {steps[activeStep]?.label} </Typography> <Box sx={{ mt: 3, mb: 0 }}> {steps[activeStep]?.code} <MobileStepper sx={{ pb: 0, pt: 0, px: 0 }} variant="text" steps={steps.length} position="static" activeStep={activeStep} nextButton={activeStep !== 2 ? ( <form onSubmit={steps[activeStep]?.formik.handleSubmit}> <Button size="small" type="submit"> Вперед <KeyboardArrowRightIcon /> </Button> </form> ) : (<Button disabled sx={{ width: '80px' }} />)} backButton={activeStep !== 0 ? ( <Button size="small" onClick={handleBack}> <KeyboardArrowLeftIcon /> Назад </Button> ) : (<Button disabled sx={{ width: '95px' }} />)} /> </Box> </Paper> </Box> </Container> ); }