315 lines
9.5 KiB
TypeScript
315 lines
9.5 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Box, Typography } from '@mui/material';
|
|
import { useTranslation } from 'react-i18next';
|
|
import Logo from '@/components/Logo';
|
|
import { PersonalInfoFields } from '../components/UserCompletionForm/PersonalInfoFields';
|
|
import { PasswordSection } from '../components/UserCompletionForm/PasswordSection';
|
|
import { EmailSection } from '../components/UserCompletionForm/EmailSection';
|
|
import { SubmitSection } from '../components/UserCompletionForm/SubmitSection';
|
|
import { useToast } from '@rkheftan/harmony-ui';
|
|
import { toLocaleDigits } from '../../../utils/persianDigit';
|
|
import i18n from '@/config/i18n';
|
|
import { Gender } from '../types/settingForm';
|
|
import { useApi } from '@/hooks/useApi';
|
|
import {
|
|
sendEmailOtpApi,
|
|
confirmEmailOtpApi,
|
|
completeUserInformationApi,
|
|
} from '../api/userCompletion';
|
|
import { containsSymbol } from '@/utils/regexes/containsSymbol';
|
|
import { least8Chars } from '@/utils/regexes/least8Chars';
|
|
import { containsNumber } from '@/utils/regexes/containsNumber';
|
|
import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
|
|
import { isEmail } from '@/utils/regexes/isEmail';
|
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
|
|
export function UserCompletionPage() {
|
|
const [params] = useSearchParams();
|
|
const { t } = useTranslation('completionForm');
|
|
const showToast = useToast();
|
|
const navigate = useNavigate();
|
|
|
|
const [firstName, setFirstName] = useState('');
|
|
const [lastName, setLastName] = useState('');
|
|
const [nationalId, setNationalId] = useState('');
|
|
const [birthDate, setBirthDate] = useState<Date | null>(null);
|
|
const [sex, setSex] = useState<Gender>(Gender.Female);
|
|
const [country, setCountry] = useState('');
|
|
const [showPasswordSection, setShowPasswordSection] = useState(false);
|
|
const [password, setPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [showEmail, setShowEmail] = useState(false);
|
|
const [email, setEmail] = useState('');
|
|
const [verificationCode, setVerificationCode] = useState('');
|
|
|
|
const [codeSent, setCodeSent] = useState(false);
|
|
const [buttonState, setButtonState] = useState<'default' | 'counting'>(
|
|
'default',
|
|
);
|
|
const [countdown, setCountdown] = useState(0);
|
|
const [emailVerified, setEmailVerified] = useState(false);
|
|
const [showPasswordValidations, setShowPasswordValidations] = useState(false);
|
|
|
|
const hasNumber = containsNumber(password);
|
|
const hasMinLength = least8Chars(password);
|
|
const hasUpperAndLower = hasUpperAndLowerLetter(password);
|
|
const hasSpecialChar = containsSymbol(password);
|
|
const validPassword =
|
|
hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar;
|
|
|
|
const matchPassword = password === confirmPassword;
|
|
const correctEmail = isEmail(email);
|
|
|
|
const { execute: sendCode, data: sendCodeData } = useApi(sendEmailOtpApi);
|
|
|
|
const {
|
|
execute: verifyCode,
|
|
data: verifyCodeData,
|
|
loading: isVerifyingCode,
|
|
} = useApi(confirmEmailOtpApi);
|
|
|
|
const {
|
|
execute: submitForm,
|
|
data: submitData,
|
|
loading: isSubmitting,
|
|
error: submitError,
|
|
} = useApi(completeUserInformationApi);
|
|
|
|
const getErrorMessage = (error: unknown): string | null => {
|
|
if (!error) return null;
|
|
if (error instanceof Error) return error.message;
|
|
return String(error);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (sendCodeData) {
|
|
if (sendCodeData.data.success) {
|
|
showToast({
|
|
message:
|
|
sendCodeData.data.message || t('completion.successfullCodeSent'),
|
|
severity: 'success',
|
|
});
|
|
setCodeSent(true);
|
|
setButtonState('counting');
|
|
setCountdown(120);
|
|
} else {
|
|
showToast({
|
|
message: sendCodeData.data.message || t('completion.problem'),
|
|
severity: 'error',
|
|
});
|
|
}
|
|
}
|
|
}, [sendCodeData, showToast, t]);
|
|
|
|
useEffect(() => {
|
|
if (verifyCodeData) {
|
|
if (verifyCodeData.data.success) {
|
|
setEmailVerified(true);
|
|
showToast({
|
|
message: verifyCodeData.data.message || t('completion.codeVerified'),
|
|
severity: 'success',
|
|
});
|
|
} else {
|
|
showToast({
|
|
message: verifyCodeData.data.message || t('completion.invalidCode'),
|
|
severity: 'error',
|
|
});
|
|
setEmailVerified(false);
|
|
}
|
|
}
|
|
}, [verifyCodeData, showToast, t]);
|
|
|
|
useEffect(() => {
|
|
if (submitData) {
|
|
showToast({
|
|
message:
|
|
submitData.data.message ||
|
|
t(
|
|
submitData.data.success
|
|
? 'completion.submitSuccess'
|
|
: 'completion.submitError',
|
|
),
|
|
severity: submitData.data.success ? 'success' : 'error',
|
|
});
|
|
|
|
if (submitData.data.success) {
|
|
const returnUrl = params.get('returnUrl');
|
|
|
|
navigate(
|
|
returnUrl
|
|
? `/account-created?returnUrl=${returnUrl}`
|
|
: '`/account-created',
|
|
);
|
|
}
|
|
} else if (submitError) {
|
|
showToast({
|
|
message: getErrorMessage(submitError) || t('completion.problem'),
|
|
severity: 'error',
|
|
});
|
|
}
|
|
}, [submitData, submitError, showToast, t, navigate, params]);
|
|
|
|
useEffect(() => {
|
|
setShowPasswordValidations(password ? !validPassword : false);
|
|
}, [password, validPassword]);
|
|
|
|
useEffect(() => {
|
|
let timer: NodeJS.Timeout;
|
|
if (buttonState === 'counting' && countdown > 0) {
|
|
timer = setInterval(() => {
|
|
setCountdown((prev) => {
|
|
if (prev <= 1) {
|
|
setButtonState('default');
|
|
clearInterval(timer);
|
|
return 0;
|
|
}
|
|
return prev - 1;
|
|
});
|
|
}, 1000);
|
|
}
|
|
return () => clearInterval(timer);
|
|
}, [buttonState, countdown]);
|
|
|
|
const handleSendCode = () => {
|
|
sendCode({ email });
|
|
};
|
|
|
|
const handleVerifyCode = () => {
|
|
if (!verificationCode.trim()) {
|
|
showToast({
|
|
message: 'Please enter the verification code',
|
|
severity: 'warning',
|
|
});
|
|
return;
|
|
}
|
|
verifyCode({ email, otpCode: verificationCode });
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
submitForm({
|
|
firstName,
|
|
lastName,
|
|
gender: sex,
|
|
nationalId,
|
|
savePassword: showPasswordSection,
|
|
password: showPasswordSection ? password : undefined,
|
|
saveEmail: showEmail,
|
|
email: showEmail ? email : undefined,
|
|
birthDate,
|
|
country,
|
|
});
|
|
};
|
|
|
|
const getButtonLabel = () => {
|
|
if (buttonState === 'counting') {
|
|
const m = String(Math.floor(countdown / 60)).padStart(2, '0');
|
|
const s = String(countdown % 60).padStart(2, '0');
|
|
return toLocaleDigits(`${m}:${s}`, i18n.language);
|
|
}
|
|
return t('completion.vericationCodeButton');
|
|
};
|
|
|
|
const handleEditEmail = () => {
|
|
setButtonState('default');
|
|
setCodeSent(false);
|
|
setEmailVerified(false);
|
|
setVerificationCode('');
|
|
};
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
backgroundColor: 'background.default',
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
p: { xs: 1, sm: 2, md: 3 },
|
|
}}
|
|
>
|
|
<Box sx={{ mb: 2 }}>
|
|
<Logo />
|
|
</Box>
|
|
<Box
|
|
sx={{
|
|
width: '100%',
|
|
maxWidth: 730,
|
|
backgroundColor: 'background.paper',
|
|
border: '1px solid #ccc',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 2,
|
|
px: { xs: 2, sm: 4, md: 6 },
|
|
py: { xs: 3, sm: 4 },
|
|
}}
|
|
>
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
<Typography variant="h5" color="text.primary">
|
|
{t('completion.title')}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{t('completion.description')}
|
|
</Typography>
|
|
</Box>
|
|
|
|
<PersonalInfoFields
|
|
firstName={firstName}
|
|
setFirstName={setFirstName}
|
|
lastName={lastName}
|
|
setLastName={setLastName}
|
|
nationalId={nationalId}
|
|
setNationalId={setNationalId}
|
|
birthDate={birthDate}
|
|
setBirthDate={setBirthDate}
|
|
sex={sex}
|
|
setSex={setSex}
|
|
country={country}
|
|
setCountry={setCountry}
|
|
/>
|
|
|
|
<PasswordSection
|
|
showPasswordSection={showPasswordSection}
|
|
setShowPasswordSection={setShowPasswordSection}
|
|
password={password}
|
|
setPassword={setPassword}
|
|
confirmPassword={confirmPassword}
|
|
setConfirmPassword={setConfirmPassword}
|
|
matchPassword={matchPassword}
|
|
hasNumber={hasNumber}
|
|
hasMinLength={hasMinLength}
|
|
hasUpperAndLower={hasUpperAndLower}
|
|
hasSpecialChar={hasSpecialChar}
|
|
validPassword={validPassword}
|
|
showValidations={showPasswordValidations}
|
|
/>
|
|
|
|
<EmailSection
|
|
showEmail={showEmail}
|
|
setShowEmail={setShowEmail}
|
|
email={email}
|
|
setEmail={setEmail}
|
|
correctEmail={correctEmail}
|
|
codeSent={codeSent}
|
|
verificationCode={verificationCode}
|
|
setVerificationCode={setVerificationCode}
|
|
buttonState={buttonState}
|
|
getButtonLabel={getButtonLabel}
|
|
handleSendCode={handleSendCode}
|
|
handleVerifyCode={handleVerifyCode}
|
|
emailVerified={emailVerified}
|
|
loading={isVerifyingCode}
|
|
handleEditEmail={handleEditEmail}
|
|
/>
|
|
|
|
<SubmitSection
|
|
onSubmit={handleSubmit}
|
|
loading={isSubmitting}
|
|
error={getErrorMessage(submitError)}
|
|
success={!!submitData?.data.success}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|