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 { 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'; import { FlexBox } from '@/components/common/FlexBox'; import { AuthenticationCard } from '../components/AuthenticationCard'; import { nationalIdRegex } from '@/utils/regexes/nationalId'; 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(null); const [sex, setSex] = useState(null); 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 [errors, setErrors] = useState<{ [key: string]: string }>({}); const [touched, setTouched] = useState<{ [key: string]: boolean }>({}); 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, loading: isSendingCode } = useApi(sendEmailOtpApi); const { execute: verifyCode, loading: isVerifyingCode } = useApi(confirmEmailOtpApi); const { execute: submitForm, loading: isSubmitting } = useApi( completeUserInformationApi, ); 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]); useEffect(() => { if (buttonState === 'default') { setCodeSent(false); setVerificationCode(''); // setEmailVerified(false); setTouched((prev) => { const n = { ...prev }; delete n.verificationCode; return n; }); setErrors((prev) => { const n = { ...prev }; delete n.verificationCode; return n; }); } }, [buttonState]); const handleSendCode = async () => { if (!isEmail(email)) { setTouched((prev) => ({ ...prev, email: true })); setErrors((prev) => ({ ...prev, email: t('validation.emailInvalid') })); return; } setTouched((prev) => { const newTouched = { ...prev }; delete newTouched.verificationCode; return newTouched; }); setErrors((prev) => { const newErrors = { ...prev }; delete newErrors.verificationCode; return newErrors; }); const res = await sendCode({ email }); if (res) { if (res.success) { showToast({ message: t('completion.successfulCodeSent'), severity: 'success', }); setCodeSent(true); setButtonState('counting'); setCountdown(120); } else { showToast({ message: res.message || t('completion.problem'), severity: 'error', }); } } }; const handleVerifyCode = async () => { const trimmedCode = verificationCode.trim(); if (!trimmedCode) { setTouched((prev) => ({ ...prev, verificationCode: true })); setErrors((prev) => ({ ...prev, verificationCode: t('validation.verificationCodeRequired'), })); return; } else if (trimmedCode.length < 4) { setTouched((prev) => ({ ...prev, verificationCode: true })); setErrors((prev) => ({ ...prev, verificationCode: t('validation.verificationCodeInvalid'), })); return; } const res = await verifyCode({ email, otpCode: verificationCode }); if (res) { if (res.success) { setEmailVerified(true); showToast({ message: res.message || t('completion.codeVerified'), severity: 'success', }); } else { showToast({ message: res.message || t('completion.invalidCode'), severity: 'error', }); setEmailVerified(false); } } }; const handleSubmit = async () => { setTouched({ firstName: true, lastName: true, country: true, email: showEmail, nationalId: true, verificationCode: showEmail, password: showPasswordSection, confirmPassword: showPasswordSection, sex: true, }); const isValid = validateForm(); if (!isValid) { return; } const res = await submitForm({ firstName, lastName, gender: sex || 0, nationalCode: nationalId, savePassword: showPasswordSection, password: showPasswordSection ? password : undefined, saveEmail: showEmail, email: showEmail ? email : undefined, birthDate, countryCode: country, }); if (res) { if (res.success) { showToast({ message: res.message || t('completion.submitSuccess'), severity: 'success', }); const returnUrl = params.get('returnUrl'); navigate( returnUrl ? `/account-created?returnUrl=${returnUrl}` : '/setting', ); } else { showToast({ message: res.message || t('completion.submitError'), severity: 'error', }); } } }; const handleEditEmail = () => { setButtonState('default'); setCodeSent(false); setEmailVerified(false); setVerificationCode(''); setTouched((prev) => { const newTouched = { ...prev }; delete newTouched.email; delete newTouched.verificationCode; return newTouched; }); setErrors((prev) => { const newErrors = { ...prev }; delete newErrors.email; delete newErrors.verificationCode; return newErrors; }); }; const validateForm = () => { const newErrors: { [key: string]: string } = {}; if (!firstName.trim()) newErrors.firstName = t('validation.firstNameRequired'); if (!lastName.trim()) newErrors.lastName = t('validation.lastNameRequired'); if (!country) newErrors.country = t('validation.countryRequired'); if (showEmail && !isEmail(email)) { newErrors.email = t('validation.emailInvalid'); } if (nationalId && !nationalIdRegex.test(nationalId)) { newErrors.nationalId = t('validation.nationalIdInvalid'); } if (showEmail && codeSent && !emailVerified) { const trimmedCode = verificationCode.trim(); if (!trimmedCode) { newErrors.verificationCode = t('validation.verificationCodeRequired'); } else if (trimmedCode.length < 4) { newErrors.verificationCode = t('validation.verificationCodeInvalid'); } else { newErrors.verificationCode = t('validation.mustVerifyCode'); } } if (showPasswordSection) { if (!password.trim()) { newErrors.password = t('validation.passwordRequired'); } else if (!validPassword) { newErrors.password = t('validation.passwordInvalid'); } if (!confirmPassword.trim()) { newErrors.confirmPassword = t('validation.confirmPasswordRequired'); } else if (!matchPassword) { newErrors.confirmPassword = t('validation.passwordsDoNotMatch'); } } if (sex === null) { newErrors.sex = t('validation.genderRequired'); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleBlur = (_field: string) => {}; useEffect(() => { if (!showPasswordSection) { setTouched((prev) => { const newTouched = { ...prev }; delete newTouched.password; delete newTouched.confirmPassword; return newTouched; }); setErrors((prev) => { const newErrors = { ...prev }; delete newErrors.password; delete newErrors.confirmPassword; return newErrors; }); } }, [showPasswordSection]); useEffect(() => { if (!showEmail) { setTouched((prev) => { const newTouched = { ...prev }; delete newTouched.email; delete newTouched.verificationCode; return newTouched; }); setErrors((prev) => { const newErrors = { ...prev }; delete newErrors.email; delete newErrors.verificationCode; return newErrors; }); } }, [showEmail]); useEffect(() => { if (Object.keys(touched).length > 0) { validateForm(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ firstName, lastName, country, email, nationalId, verificationCode, codeSent, emailVerified, password, confirmPassword, showPasswordSection, sex, touched, ]); return ( { e.preventDefault(); e.stopPropagation(); void handleSubmit(); }} > {t('completion.title')} {t('completion.description')} ); }