330 lines
9.5 KiB
TypeScript
330 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 './PersonalInfoFields';
|
|
import { PasswordSection } from './PasswordSection';
|
|
import { EmailSection } from './EmailSection';
|
|
import { SubmitSection } from './SubmitSection';
|
|
import { useToast } from '@rkheftan/harmony-ui';
|
|
import { regex } from '../../../utils/regex';
|
|
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 {
|
|
type SendEmailOtpPayload,
|
|
type ConfirmEmailOtpPayload,
|
|
type CompleteUserInfoPayload,
|
|
} from '../types/completionFormApiTypes';
|
|
import { type ApiResponse } from '@/types/apiResponse';
|
|
|
|
export function UserCompletionForm() {
|
|
const { t } = useTranslation('completionForm');
|
|
const showToast = useToast();
|
|
|
|
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,
|
|
hasMinLength,
|
|
hasUpperAndLower,
|
|
hasSpecialChar,
|
|
validPassword,
|
|
correctEmail,
|
|
} = regex(password, email);
|
|
const matchPassword = password === confirmPassword;
|
|
|
|
const { execute: sendCode, data: sendCodeData } = useApi(
|
|
async (payload: SendEmailOtpPayload) => {
|
|
const result = await sendEmailOtpApi(payload);
|
|
const conformingResult: ApiResponse = {
|
|
...result,
|
|
errorCode: result.errorCode || 0,
|
|
validations: [],
|
|
};
|
|
return { data: conformingResult };
|
|
},
|
|
);
|
|
|
|
const {
|
|
execute: verifyCode,
|
|
loading: isVerifyingCode,
|
|
data: verifyCodeData,
|
|
} = useApi(async (payload: ConfirmEmailOtpPayload) => {
|
|
const result = await confirmEmailOtpApi(payload);
|
|
const conformingResult: ApiResponse = {
|
|
...result,
|
|
errorCode: result.errorCode || 0,
|
|
validations: [],
|
|
};
|
|
return { data: conformingResult };
|
|
});
|
|
|
|
const {
|
|
execute: submitForm,
|
|
loading: isSubmitting,
|
|
error: submitError,
|
|
data: submitData,
|
|
} = useApi(async (payload: CompleteUserInfoPayload) => {
|
|
const result = await completeUserInformationApi(payload);
|
|
const conformingResult: ApiResponse = {
|
|
...result,
|
|
errorCode: result.errorCode || 0,
|
|
validations: [],
|
|
};
|
|
return { data: conformingResult };
|
|
});
|
|
|
|
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.success) {
|
|
showToast({
|
|
message: sendCodeData.message || t('completion.successfullCodeSent'),
|
|
severity: 'success',
|
|
});
|
|
setCodeSent(true);
|
|
setButtonState('counting');
|
|
setCountdown(120);
|
|
} else {
|
|
showToast({
|
|
message: sendCodeData.message || t('completion.problem'),
|
|
severity: 'error',
|
|
});
|
|
}
|
|
}
|
|
}, [sendCodeData, showToast, t]);
|
|
|
|
useEffect(() => {
|
|
if (verifyCodeData) {
|
|
if (verifyCodeData.success) {
|
|
setEmailVerified(true);
|
|
showToast({
|
|
message: verifyCodeData.message || t('completion.codeVerified'),
|
|
severity: 'success',
|
|
});
|
|
} else {
|
|
showToast({
|
|
message: verifyCodeData.message || t('completion.invalidCode'),
|
|
severity: 'error',
|
|
});
|
|
setEmailVerified(false);
|
|
}
|
|
}
|
|
}, [verifyCodeData, showToast, t]);
|
|
|
|
useEffect(() => {
|
|
if (submitData) {
|
|
showToast({
|
|
message:
|
|
submitData.message ||
|
|
t(
|
|
submitData.success
|
|
? 'completion.submitSuccess'
|
|
: 'completion.submitError',
|
|
),
|
|
severity: submitData.success ? 'success' : 'error',
|
|
});
|
|
} else if (submitError) {
|
|
showToast({
|
|
message: getErrorMessage(submitError) || t('completion.problem'),
|
|
severity: 'error',
|
|
});
|
|
}
|
|
}, [submitData, submitError, showToast, t]);
|
|
|
|
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({
|
|
userId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
|
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?.success}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|