diff --git a/public/locales/fa/completionForm.json b/public/locales/fa/completionForm.json new file mode 100644 index 0000000..bd9a9bd --- /dev/null +++ b/public/locales/fa/completionForm.json @@ -0,0 +1,30 @@ +{ + "completion": { + "title": "تکمیل اطلاعات حساب کاربری", + "description": "اطلاعات کسب و کار خود را وارد کنید", + "name": "نام", + "familyName": "نام خانوادگی", + "gender": "جنسیت", + "optionalNationalCode": "کدملی(اختیاری)", + "determinePassword": "تعیین رمز عبور", + "password": "رمز عبور", + "passwordRepetition": "تکرار رمز عبور", + "determineEmail": "اتصال ایمیل خود", + "email": "ایمیل", + "vericationCodeButton": "ارسال کد تایید", + "verificationCode": "کد تایید", + "checkCodeButton": "بررسی کد", + "registerButton": "تایید و ثبت نام", + "man": "مرد", + "woman": "زن", + "hasNumber": "شامل عدد", + "hasMinLength": "حداقل 8 کاراکتر", + "hasUpperAndLower": "شامل یک حرف کوچک و بزرگ", + "hasSpecialChar": "شامل علامت (!@#$%^&*)", + "notCompatibility": "مطابقت ندارد", + "emailCorrectForm": "فرم درست ایمیل را وارد کنید", + "agreementPart1": " ادامه فرایند ثبت نام به منزله تایید و قبول", + "agreementLinkText": " قوانین و مقررات هارمونی", + "agreementPart2": "می باشد." + } +} diff --git a/src/features/authentication/components/EmailSection.tsx b/src/features/authentication/components/EmailSection.tsx new file mode 100644 index 0000000..ae54a59 --- /dev/null +++ b/src/features/authentication/components/EmailSection.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { + TextField, + Box, + Button, + Switch, + FormGroup, + Typography, + InputAdornment, + IconButton, +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { TickCircle, Edit, Refresh } from 'iconsax-react'; + +interface EmailSectionProps { + showEmail: boolean; + setShowEmail: (checked: boolean) => void; + email: string; + setEmail: (email: string) => void; + correctEmail: boolean; + codeSent: boolean; + verificationCode: string; + setVerificationCode: (code: string) => void; + buttonState: 'default' | 'counting' | 'sent'; + getButtonLabel: () => string; + handleSendCode: () => void; + handleVerifyCode: () => void; + emailVerified: boolean; + isVerifyingCode: boolean; + handleEditEmail: () => void; +} + +export function EmailSection({ + showEmail, + setShowEmail, + email, + setEmail, + correctEmail, + codeSent, + verificationCode, + setVerificationCode, + buttonState, + getButtonLabel, + handleSendCode, + handleVerifyCode, + emailVerified, + isVerifyingCode, + handleEditEmail, +}: EmailSectionProps) { + const { t } = useTranslation('completionForm'); + + const handleToggleEmail = (e: React.ChangeEvent) => { + setShowEmail(e.target.checked); + }; + + return ( + <> + + + + + {t('completion.determineEmail')} + + + + {showEmail && ( + + + + setEmail(e.target.value)} + sx={{ + width: !isVerifyingCode && !emailVerified ? '446px' : '634px', + transition: 'width 0.3s', + }} + InputProps={{ + startAdornment: + buttonState === 'counting' ? ( + + + + + + ) : null, + endAdornment: + !isVerifyingCode && emailVerified ? ( + + ) : null, + }} + /> + {email && ( + + {correctEmail ? '' : t('completion.emailCorrectForm')} + + )} + + {!isVerifyingCode && !emailVerified && ( + + )} + + {!emailVerified && codeSent && ( + + setVerificationCode(e.target.value)} + sx={{ width: '446px' }} + disabled={isVerifyingCode} + /> + + + )} + + )} + + ); +} diff --git a/src/features/authentication/components/PasswordSection.tsx b/src/features/authentication/components/PasswordSection.tsx new file mode 100644 index 0000000..507bc17 --- /dev/null +++ b/src/features/authentication/components/PasswordSection.tsx @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; +import { + TextField, + Box, + IconButton, + Switch, + FormGroup, + Typography, + InputAdornment, +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { TickCircle, Eye, EyeSlash } from 'iconsax-react'; +import { PasswordValidationItem } from './PasswordValidation'; + +interface PasswordSectionProps { + showPasswordSection: boolean; + setShowPasswordSection: (checked: boolean) => void; + password: string; + setPassword: (password: string) => void; + confirmPassword: string; + setConfirmPassword: (confirmPassword: string) => void; + matchPassword: boolean; + hasNumber: boolean; + hasMinLength: boolean; + hasUpperAndLower: boolean; + hasSpecialChar: boolean; + validPassword: boolean; + showValidations: boolean; +} + +export function PasswordSection({ + showPasswordSection, + setShowPasswordSection, + password, + setPassword, + confirmPassword, + setConfirmPassword, + matchPassword, + hasNumber, + hasMinLength, + hasUpperAndLower, + hasSpecialChar, + validPassword, + showValidations, +}: PasswordSectionProps) { + const { t } = useTranslation('completionForm'); + const [showPasswordText, setShowPasswordText] = useState(false); + + const handleTogglePasswordSection = ( + e: React.ChangeEvent, + ) => { + setShowPasswordSection(e.target.checked); + }; + + const handleTogglePasswordEye = () => { + setShowPasswordText((prev) => !prev); + }; + + return ( + <> + + + + + {t('completion.determinePassword')} + + + + + {showPasswordSection && ( + + + setPassword(e.target.value)} + variant="outlined" + type={showPasswordText ? 'text' : 'password'} + sx={{ width: '309px' }} + InputProps={{ + endAdornment: ( + + {validPassword && ( + + )} + + {showPasswordText ? ( + + ) : ( + + )} + + + ), + }} + /> + + {password && ( + + {showValidations && ( + + + + + + + )} + + )} + + + setConfirmPassword(e.target.value)} + error={confirmPassword.length > 0 && !matchPassword} + helperText={ + confirmPassword.length > 0 && !matchPassword + ? t('completion.notCompatibility') + : ' ' + } + sx={{ width: '309px' }} + type={showPasswordText ? 'text' : 'password'} + InputProps={{ + endAdornment: ( + + {confirmPassword.length > 0 && matchPassword && ( + + )} + + {showPasswordText ? ( + + ) : ( + + )} + + + ), + }} + /> + + )} + + ); +} diff --git a/src/features/authentication/components/PasswordValidation.tsx b/src/features/authentication/components/PasswordValidation.tsx new file mode 100644 index 0000000..97154fe --- /dev/null +++ b/src/features/authentication/components/PasswordValidation.tsx @@ -0,0 +1,25 @@ +import { Box, Typography } from '@mui/material'; +import { TickCircle } from 'iconsax-react'; + +interface ValidationItemProps { + isValid: boolean; + label: string; +} + +export function PasswordValidationItem({ + isValid, + label, +}: ValidationItemProps) { + return ( + + + + {label} + + + ); +} diff --git a/src/features/authentication/components/PersonalInfoFields.tsx b/src/features/authentication/components/PersonalInfoFields.tsx new file mode 100644 index 0000000..fab1045 --- /dev/null +++ b/src/features/authentication/components/PersonalInfoFields.tsx @@ -0,0 +1,71 @@ +import { + TextField, + FormControl, + InputLabel, + MenuItem, + Select, + Box, + type SelectChangeEvent, +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +interface PersonalInfoFieldsProps { + sex: string; + setSex: (sex: string) => void; +} + +export function PersonalInfoFields({ sex, setSex }: PersonalInfoFieldsProps) { + const { t } = useTranslation('completionForm'); + + const handleChange = (e: SelectChangeEvent) => { + setSex(e.target.value); + }; + + return ( + + + + + + + + + {t('completion.gender')} + + + + + + + ); +} diff --git a/src/features/authentication/components/SubmitSection.tsx b/src/features/authentication/components/SubmitSection.tsx new file mode 100644 index 0000000..7e7213a --- /dev/null +++ b/src/features/authentication/components/SubmitSection.tsx @@ -0,0 +1,24 @@ +import { Box, Button, Typography, Link } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export function SubmitSection() { + const { t } = useTranslation('completionForm'); + + return ( + + + {t('completion.agreementPart1')} + + {t('completion.agreementLinkText')} + {' '} + {t('completion.agreementPart2')} + + + + ); +} diff --git a/src/features/authentication/components/UserCompletionForm.tsx b/src/features/authentication/components/UserCompletionForm.tsx index ef4f8e0..2c602aa 100644 --- a/src/features/authentication/components/UserCompletionForm.tsx +++ b/src/features/authentication/components/UserCompletionForm.tsx @@ -1,56 +1,53 @@ -import { - TextField, - FormControl, - InputLabel, - MenuItem, - Select, - Box, - type SelectChangeEvent, - Switch, - FormGroup, - Button, - Typography, - Link, -} from '@mui/material'; -import React, { useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PersonalInfoFields } from './PersonalInfoFields'; +import { PasswordSection } from './PasswordSection'; +import { EmailSection } from './EmailSection'; +import { SubmitSection } from './SubmitSection'; export function UserCompletionForm() { + const { t } = useTranslation('completionForm'); const [sex, setSex] = useState(''); - const [showPassword, setShowPassword] = useState(false); - const [showEmail, setShowEmail] = useState(false); + const [showPasswordSection, setShowPasswordSection] = useState(false); const [password, setPassword] = useState(''); - const [email, setEmail] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); + const [showEmail, setShowEmail] = useState(false); + const [email, setEmail] = useState(''); const [codeSent, setCodeSent] = useState(false); const [verificationCode, setVerificationCode] = useState(''); - const [buttonState, setButtonState] = useState('default'); // default | counting | sent + const [buttonState, setButtonState] = useState< + 'default' | 'counting' | 'sent' + >('default'); // default | counting | sent const [countdown, setCountdown] = useState(60); + const [emailVerified, setEmailVerified] = useState(false); + const [isVerifyingCode, setIsVerifyingCode] = useState(false); + const matchPassword = password === confirmPassword; const hasNumber = /\d/.test(password); const hasMinLength = password.length >= 8; const hasUpperAndLower = /[A-Z]/.test(password) && /[a-z]/.test(password); const hasSpecialChar = /[!@#$%^&*]/.test(password); + const validPassword = + hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar; + const [showPasswordValidations, setShowPasswordValidations] = useState(false); + const correctEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); - const handleTogglePassword = (e: React.ChangeEvent) => { - setShowPassword(e.target.checked); - }; - const handleToggleEmail = (e: React.ChangeEvent) => { - setShowEmail(e.target.checked); - }; - - const handleChange = (e: SelectChangeEvent) => { - setSex(e.target.value); - }; - - const onClickCodeSent = () => { - setCodeSent(true); - setButtonState('sent'); - setTimeout(() => { - setButtonState('counting'); - setCountdown(60); - }, 1000); - }; + useEffect(() => { + if (password) { + if (!validPassword) { + setShowPasswordValidations(true); + } else { + const timer = setTimeout(() => { + setShowPasswordValidations(false); + }, 1000); + return () => clearTimeout(timer); + } + } else { + setShowPasswordValidations(false); + } + }, [password, validPassword]); useEffect(() => { let timer: ReturnType; @@ -76,282 +73,112 @@ export function UserCompletionForm() { const time = `${minutes}:${seconds}`; return toPersianDigits(time); } - return 'ارسال کد تایید'; + return t('completion.vericationCodeButton'); + }; + + const handleSendCode = () => { + setCodeSent(true); + setButtonState('sent'); + setTimeout(() => { + setButtonState('counting'); + setCountdown(60); + }, 1000); + }; + + const handleVerifyCode = () => { + setIsVerifyingCode(true); + setTimeout(() => { + setIsVerifyingCode(false); + setEmailVerified(true); + }, 1000); + }; + + const handleEditEmail = () => { + setButtonState('default'); + setCodeSent(false); + setEmailVerified(false); }; return ( -
- - - تکمیل اطلاعات حساب کاربری - - - اطلاعات کسب و کار خود را وارد کنید - - - - - - - - جنسیت - - - - - - - - - تعیین رمز عبور - - - {showPassword && ( - - - setPassword(e.target.value)} - variant="outlined" - sx={{ - '& .MuiOutlinedInput-root': { - height: 45, - }, - }} - /> - {password && ( - - - شامل عدد - -
- - حداقل 8 کاراکتر - -
- - شامل یک حرف بزرگ و کوچک - -
- - شامل علامت(!@#$%^&*) - -
- )} -
- {showPassword && ( - setConfirmPassword(e.target.value)} - error={confirmPassword.length > 0 && !matchPassword} - helperText={ - confirmPassword.length > 0 && !matchPassword - ? 'مطابقت ندارد' - : ' ' - } - sx={{ - width: '330px', - '& .MuiOutlinedInput-root': { - height: 45, - }, - }} - /> - )} -
- )} - - - - - اتصال ایمیل خود - - - {showEmail && ( - - - - setEmail(e.target.value)} - sx={{ - width: '330px', - '& .MuiOutlinedInput-root': { - height: 45, - }, - }} - /> - {email && ( - - فرم درست ایمیل وارد کنید - - )} - - - - {codeSent && ( - - setVerificationCode(e.target.value)} - sx={{ - width: '330px', - '& .MuiOutlinedInput-root': { - height: 45, - }, - }} - /> - - - )} - - )} - - - ادامه فرایند ثبت نام به منزله تایید و قبول{' '} - - قوانین و مقررات هارمونی - {' '} - می باشد. - - + {t('completion.title')} + + + {t('completion.description')} + + + + + + + + +
-
+ ); } diff --git a/src/features/profile/components/PersonalInformation.tsx b/src/features/profile/components/PersonalInformation.tsx deleted file mode 100644 index c48c578..0000000 --- a/src/features/profile/components/PersonalInformation.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { - Box, - Typography, - Button, - TextField, - FormControl, - Select, - MenuItem, - type SelectChangeEvent, -} from '@mui/material'; -import { useState, type ChangeEvent } from 'react'; -import { CardContainer } from '@/components/CardContainer'; - -export function PersonalInformation() { - const [isEditing, setIsEditing] = useState(false); - const [gender, setGender] = useState(''); - const [data, setData] = useState({ - firstName: 'محمد حسین', - lastName: 'برزه‌گر', - gender: 'مرد', - nationalCode: '', - }); - - const handleChange = (e: ChangeEvent) => { - setData((prev) => ({ - ...prev, - [e.target.name]: e.target.value, - })); - }; - - const toggleEdit = () => { - setIsEditing((prev) => !prev); - if (isEditing) { - setData((prev) => ({ - ...prev, - gender: gender === 'male' ? 'مرد' : gender === 'female' ? 'زن' : '', - })); - } else { - setGender( - data.gender === 'مرد' ? 'male' : data.gender === 'زن' ? 'female' : '', - ); - } - }; - - const handleChangeGender = (e: SelectChangeEvent) => { - setGender(e.target.value); - }; - - const displayValue = (value: string | null | undefined) => { - return value && value.trim() !== '' ? value : 'تعیین نشده'; - }; - - return ( - - - {isEditing && ( - - )} - - - } - > - - - {isEditing ? ( - - ) : ( - - - نام - - - {displayValue(data.firstName)} - - - )} - - - - {isEditing ? ( - - ) : ( - - - نام خانوادگی - - - {displayValue(data.lastName)} - - - )} - - - - {isEditing ? ( - - - - ) : ( - - - جنسیت - - - {displayValue(data.gender)} - - - )} - - - - {isEditing ? ( - - ) : ( - - - کد ملی - - - {displayValue(data.nationalCode)} - - - )} - - - - - ); -}