feat: layout padding and scroll added
This commit is contained in:
20
src/features/authentication/routes/AccountCreatedPage.tsx
Normal file
20
src/features/authentication/routes/AccountCreatedPage.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { FlexBox } from '@/components/common/FlexBox';
|
||||
import Logo from '@/components/Logo';
|
||||
import { AccountCreated } from '../components/AccountCreated/AccountCreated';
|
||||
|
||||
export const AccountCreatedPage = () => {
|
||||
return (
|
||||
<FlexBox
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
sx={{
|
||||
minHeight: '100vh',
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
<AccountCreated />
|
||||
</FlexBox>
|
||||
);
|
||||
};
|
||||
20
src/features/authentication/routes/AuthenticationPage.tsx
Normal file
20
src/features/authentication/routes/AuthenticationPage.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { FlexBox } from '@/components/common/FlexBox';
|
||||
import Logo from '@/components/Logo';
|
||||
import { AuthenticationSteps } from '../components/AuthenticationSteps/AuthenticationSteps';
|
||||
|
||||
export function AuthenticationPage() {
|
||||
return (
|
||||
<FlexBox
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
sx={{
|
||||
minHeight: '100vh',
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
<AuthenticationSteps />
|
||||
</FlexBox>
|
||||
);
|
||||
}
|
||||
20
src/features/authentication/routes/ForgetPasswordPage.tsx
Normal file
20
src/features/authentication/routes/ForgetPasswordPage.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { FlexBox } from '@/components/common/FlexBox';
|
||||
import Logo from '@/components/Logo';
|
||||
import { ForgetPasswordContainer } from '../components/ForgetPassword/ForgetPasswordContainer';
|
||||
|
||||
export function ForgetPasswordPage() {
|
||||
return (
|
||||
<FlexBox
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
sx={{
|
||||
minHeight: '100vh',
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
<ForgetPasswordContainer />
|
||||
</FlexBox>
|
||||
);
|
||||
}
|
||||
314
src/features/authentication/routes/UserCompletionPage.tsx
Normal file
314
src/features/authentication/routes/UserCompletionPage.tsx
Normal file
@@ -0,0 +1,314 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user