From 09e4dfa9171e32d8d0c013a3fb57e70b696535a2 Mon Sep 17 00:00:00 2001 From: Koosha Lahouti Date: Tue, 22 Jul 2025 17:36:35 +0330 Subject: [PATCH] feat: country selection, add birthdate and add error messages for textfields --- public/locales/fa/completionForm.json | 7 +- .../components/EmailSection.tsx | 19 +- .../components/PasswordSection.tsx | 208 ++++++++++-------- .../components/PersonalInfoFields.tsx | 195 +++++++--------- 4 files changed, 220 insertions(+), 209 deletions(-) diff --git a/public/locales/fa/completionForm.json b/public/locales/fa/completionForm.json index abcf0d7..f30e60c 100644 --- a/public/locales/fa/completionForm.json +++ b/public/locales/fa/completionForm.json @@ -21,13 +21,14 @@ "hasMinLength": "حداقل 8 کاراکتر", "hasUpperAndLower": "شامل یک حرف کوچک و بزرگ", "hasSpecialChar": "شامل علامت (!@#$%^&*)", - "notCompatibility": "مطابقت ندارد", - "emailCorrectForm": "فرم درست ایمیل را وارد کنید", + "notCompatibility": "تکرار رمز عبور با رمز عبور یکسان نمی باشد", + "emailCorrectForm": "ساختار ایمیل صحیح نیست", "agreementPart1": " ادامه فرایند ثبت نام به منزله تایید و قبول", "agreementLinkText": " قوانین و مقررات هارمونی", "agreementPart2": "می باشد.", "sent": "ارسال شد!", "country": "کشور", - "dateOfBirth": "تاریخ تولد(اختیاری)" + "dateOfBirth": "تاریخ تولد(اختیاری)", + "invalidCountry": "کشور انتخاب شده صحیح نیست" } } diff --git a/src/features/authentication/components/EmailSection.tsx b/src/features/authentication/components/EmailSection.tsx index 67d5bf8..159cb3c 100644 --- a/src/features/authentication/components/EmailSection.tsx +++ b/src/features/authentication/components/EmailSection.tsx @@ -72,6 +72,7 @@ export function EmailSection({ variant="outlined" value={email} onChange={(e) => setEmail(e.target.value)} + error={!correctEmail} sx={{ width: !isVerifyingCode && !emailVerified ? '446px' : '634px', transition: 'width 0.3s', @@ -111,17 +112,25 @@ export function EmailSection({ }, }} /> + {email && ( + + {correctEmail ? '' : t('completion.emailCorrectForm')} + + )} {!isVerifyingCode && !emailVerified && ( diff --git a/src/features/authentication/components/PasswordSection.tsx b/src/features/authentication/components/PasswordSection.tsx index 3194b07..308734d 100644 --- a/src/features/authentication/components/PasswordSection.tsx +++ b/src/features/authentication/components/PasswordSection.tsx @@ -9,7 +9,7 @@ import { InputAdornment, } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { TickCircle, Eye, EyeSlash } from 'iconsax-react'; +import { TickCircle, Eye, EyeSlash, CloseCircle } from 'iconsax-react'; import { PasswordValidationItem } from './PasswordValidation'; interface PasswordSectionProps { @@ -66,10 +66,12 @@ export function PasswordSection({ <> - + + + @@ -79,8 +81,8 @@ export function PasswordSection({ {showPasswordSection && ( - - + + - {password && ( - - {showValidations && ( - - - - - - - )} - - )} + setConfirmPassword(e.target.value)} + error={confirmPassword.length > 0 && !matchPassword} + helperText={ + confirmPassword.length > 0 && !matchPassword + ? t('completion.notCompatibility') + : ' ' + } + sx={{ width: '309px' }} + type={showPasswordRepititonText ? 'text' : 'password'} + InputProps={{ + endAdornment: ( + + + {confirmPassword.length > 0 ? ( + matchPassword ? ( + + ) : ( + + ) + ) : null} + + 0 && matchPassword + ? 0.5 + : 'auto', + }} + > + {showPasswordRepititonText ? ( + + ) : ( + + )} + + + + ), + }} + inputProps={{ + style: { + paddingRight: + confirmPassword.length > 0 && matchPassword + ? '48px' + : '45px', + }, + }} + /> - setConfirmPassword(e.target.value)} - error={confirmPassword.length > 0 && !matchPassword} - helperText={ - confirmPassword.length > 0 && !matchPassword - ? t('completion.notCompatibility') - : ' ' - } - sx={{ width: '309px' }} - type={showPasswordRepititonText ? 'text' : 'password'} - InputProps={{ - endAdornment: ( - - - {confirmPassword.length > 0 && matchPassword && ( - - )} - - {showPasswordRepititonText ? ( - - ) : ( - - )} - - - - ), - }} - inputProps={{ - style: { - paddingRight: validPassword ? '48px' : '20px', - }, - }} - /> + {password && showValidations && ( + + + + + + + + + + + )} )} diff --git a/src/features/authentication/components/PersonalInfoFields.tsx b/src/features/authentication/components/PersonalInfoFields.tsx index 27ba714..ad9d5b9 100644 --- a/src/features/authentication/components/PersonalInfoFields.tsx +++ b/src/features/authentication/components/PersonalInfoFields.tsx @@ -5,9 +5,11 @@ import { MenuItem, Select, Box, - type SelectChangeEvent, + Autocomplete, } from '@mui/material'; import { useTranslation } from 'react-i18next'; +import { Woman, Man } from 'iconsax-react'; +import { useState } from 'react'; interface PersonalInfoFieldsProps { sex: string; @@ -23,116 +25,65 @@ export function PersonalInfoFields({ setCountry, }: PersonalInfoFieldsProps) { const { t } = useTranslation('completionForm'); + const [countryError, setCountryError] = useState(false); + const countries = [ { name: 'Afghanistan', fa: 'افغانستان', flag: 'af' }, { name: 'Albania', fa: 'آلبانی', flag: 'al' }, { name: 'Algeria', fa: 'الجزایر', flag: 'dz' }, - { name: 'Andorra', fa: 'آندورا', flag: 'ad' }, - { name: 'Angola', fa: 'آنگولا', flag: 'ao' }, { name: 'Argentina', fa: 'آرژانتین', flag: 'ar' }, { name: 'Armenia', fa: 'ارمنستان', flag: 'am' }, { name: 'Australia', fa: 'استرالیا', flag: 'au' }, { name: 'Austria', fa: 'اتریش', flag: 'at' }, - { name: 'Azerbaijan', fa: 'آذربایجان', flag: 'az' }, - { name: 'Bahamas', fa: 'باهاما', flag: 'bs' }, { name: 'Bahrain', fa: 'بحرین', flag: 'bh' }, - { name: 'Bangladesh', fa: 'بنگلادش', flag: 'bd' }, - { name: 'Barbados', fa: 'باربادوس', flag: 'bb' }, - { name: 'Belarus', fa: 'بلاروس', flag: 'by' }, - { name: 'Belgium', fa: 'بلژیک', flag: 'be' }, - { name: 'Belize', fa: 'بلیز', flag: 'bz' }, - { name: 'Benin', fa: 'بنین', flag: 'bj' }, - { name: 'Bhutan', fa: 'بوتان', flag: 'bt' }, - { name: 'Bolivia', fa: 'بولیوی', flag: 'bo' }, - { name: 'Bosnia and Herzegovina', fa: 'بوسنی و هرزگوین', flag: 'ba' }, - { name: 'Botswana', fa: 'بوتسوانا', flag: 'bw' }, - { name: 'Brazil', fa: 'برزیل', flag: 'br' }, - { name: 'Brunei', fa: 'برونئی', flag: 'bn' }, - { name: 'Bulgaria', fa: 'بلغارستان', flag: 'bg' }, - { name: 'Burkina Faso', fa: 'بورکینافاسو', flag: 'bf' }, - { name: 'Burundi', fa: 'بوروندی', flag: 'bi' }, - { name: 'Cambodia', fa: 'کامبوج', flag: 'kh' }, - { name: 'Cameroon', fa: 'کامرون', flag: 'cm' }, { name: 'Canada', fa: 'کانادا', flag: 'ca' }, - { name: 'Cape Verde', fa: 'کیپ ورد', flag: '🇨🇻' }, - { - name: 'Central African Republic', - fa: 'جمهوری آفریقای مرکزی', - flag: 'cf', - }, - { name: 'Chad', fa: 'چاد', flag: 'td' }, - { name: 'Chile', fa: 'شیلی', flag: 'cl' }, { name: 'China', fa: 'چین', flag: 'cn' }, - { name: 'Colombia', fa: 'کلمبیا', flag: 'co' }, - { name: 'Comoros', fa: 'کومور', flag: 'km' }, - { name: 'Congo (Brazzaville)', fa: 'کنگو (برازاویل)', flag: 'cg' }, - { name: 'Congo (Kinshasa)', fa: 'کنگو (کینشاسا)', flag: 'cd' }, - { name: 'Costa Rica', fa: 'کاستاریکا', flag: 'cr' }, - { name: 'Croatia', fa: 'کرواسی', flag: 'hr' }, - { name: 'Cuba', fa: 'کوبا', flag: 'cu' }, - { name: 'Cyprus', fa: 'قبرس', flag: 'cy' }, - { name: 'Czech Republic', fa: 'جمهوری چک', flag: 'cz' }, - { name: 'Denmark', fa: 'دانمارک', flag: 'dk' }, - { name: 'Djibouti', fa: 'جیبوتی', flag: 'dj' }, - { name: 'Dominica', fa: 'دومینیکا', flag: 'dm' }, - { name: 'Dominican Republic', fa: 'جمهوری دومینیکن', flag: 'do' }, - { name: 'Ecuador', fa: 'اکوادور', flag: 'ec' }, - { name: 'Egypt', fa: 'مصر', flag: 'eg' }, - { name: 'El Salvador', fa: 'السالوادور', flag: 'sv' }, - { name: 'Equatorial Guinea', fa: 'گینه استوایی', flag: 'gq' }, - { name: 'Eritrea', fa: 'اریتره', flag: 'er' }, - { name: 'Estonia', fa: 'استونی', flag: 'ee' }, - { name: 'Eswatini', fa: 'اسواتینی', flag: 'az' }, - { name: 'Ethiopia', fa: 'اتیوپی', flag: 'et' }, - { name: 'Fiji', fa: 'فیجی', flag: 'fj' }, - { name: 'Finland', fa: 'فنلاند', flag: 'fi' }, { name: 'France', fa: 'فرانسه', flag: 'fr' }, - { name: 'Gabon', fa: 'گابون', flag: 'ga' }, - { name: 'Gambia', fa: 'گامبیا', flag: 'gm' }, - { name: 'Georgia', fa: 'گرجستان', flag: 'ge' }, { name: 'Germany', fa: 'آلمان', flag: 'de' }, - { name: 'Ghana', fa: 'غنا', flag: 'gh' }, - { name: 'Greece', fa: 'یونان', flag: 'gr' }, - { name: 'Guatemala', fa: 'گواتمالا', flag: 'gt' }, { name: 'India', fa: 'هند', flag: 'in' }, - { name: 'Indonesia', fa: 'اندونزی', flag: 'id' }, { name: 'Iran', fa: 'ایران', flag: 'ir' }, { name: 'Iraq', fa: 'عراق', flag: 'iq' }, - { name: 'Ireland', fa: 'ایرلند', flag: 'ie' }, - { name: 'Israel', fa: 'اسرائیل', flag: 'il' }, { name: 'Italy', fa: 'ایتالیا', flag: 'it' }, { name: 'Japan', fa: 'ژاپن', flag: 'jp' }, - { name: 'Jordan', fa: 'اردن', flag: 'jo' }, - { name: 'Kazakhstan', fa: 'قزاقستان', flag: 'kz' }, - { name: 'Kuwait', fa: 'کویت', flag: 'kw' }, - { name: 'Lebanon', fa: 'لبنان', flag: 'lb' }, - { name: 'Malaysia', fa: 'مالزی', flag: 'my' }, { name: 'Netherlands', fa: 'هلند', flag: 'nl' }, - { name: 'Norway', fa: 'نروژ', flag: 'no' }, - { name: 'Oman', fa: 'عمان', flag: 'om' }, { name: 'Pakistan', fa: 'پاکستان', flag: 'pk' }, - { name: 'Palestine', fa: 'فلسطین', flag: 'ps' }, { name: 'Qatar', fa: 'قطر', flag: 'qa' }, { name: 'Russia', fa: 'روسیه', flag: 'ru' }, { name: 'Saudi Arabia', fa: 'عربستان سعودی', flag: 'sa' }, { name: 'Spain', fa: 'اسپانیا', flag: 'es' }, { name: 'Sweden', fa: 'سوئد', flag: 'se' }, { name: 'Switzerland', fa: 'سوئیس', flag: 'ch' }, - { name: 'Syria', fa: 'سوریه', flag: 'sy' }, { name: 'Turkey', fa: 'ترکیه', flag: 'tr' }, - { name: 'Ukraine', fa: 'اوکراین', flag: 'ua' }, { name: 'United Arab Emirates', fa: 'امارات متحده عربی', flag: 'ae' }, { name: 'United Kingdom', fa: 'بریتانیا', flag: 'gb' }, { name: 'United States', fa: 'ایالات متحده آمریکا', flag: 'us' }, { name: 'Yemen', fa: 'یمن', flag: 'ye' }, ]; - const handleChange = (e: SelectChangeEvent) => { + const handleChangeSex = (e: any) => { setSex(e.target.value); }; - const handleChangeCountry = (e: SelectChangeEvent) => { - setCountry(e.target.value); + const handleChangeCountry = (_: any, newValue: any) => { + setCountry(newValue ? newValue.name : ''); + setCountryError(false); + }; + const handleCountryBlur = (e: React.FocusEvent) => { + const inputValue = e.target.value.trim(); + const exists = countries.some((c) => c.fa === inputValue); + if (inputValue && !exists) { + setCountryError(true); + } else { + setCountryError(false); + } + }; + const handleInputChange = (_: any, value: string) => { + const exists = countries.some((c) => c.fa === value.trim()); + if (value.trim() && !exists) { + setCountryError(true); + } else { + setCountryError(false); + } }; return ( @@ -142,17 +93,13 @@ export function PersonalInfoFields({ label={t('completion.name')} placeholder={t('completion.name')} variant="outlined" - sx={{ - width: '309px', - }} + sx={{ width: '309px' }} /> @@ -162,10 +109,20 @@ export function PersonalInfoFields({ @@ -173,45 +130,53 @@ export function PersonalInfoFields({ label={t('completion.optionalNationalCode')} placeholder={t('completion.optionalNationalCode')} variant="outlined" - sx={{ - width: '309px', - }} + sx={{ width: '309px' }} /> + - - {t('completion.country')} - - + (typeof c === 'string' ? c : c.fa)} + value={countries.find((c) => c.name === country) || null} + onChange={handleChangeCountry} + onInputChange={handleInputChange} + freeSolo + noOptionsText="" + renderOption={(props, c) => ( + + {c.name} + {c.fa} + + )} + renderInput={(params) => ( + + )} + sx={{ width: '309px' }} + />