fix: styles

This commit is contained in:
Koosha Lahouti
2025-08-12 02:48:58 +03:30
parent 09e4dfa917
commit a782043767
11 changed files with 352 additions and 595 deletions

View File

@@ -0,0 +1,38 @@
import { useState, useMemo } from 'react';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalali';
import { useTranslation } from 'react-i18next';
export function DateOfBirth() {
const { t, i18n } = useTranslation('completionForm');
const isFarsi = i18n.language === 'fa' || i18n.language === 'fa-IR';
const [birthDate, setBirthDate] = useState<Date | null>(null);
const Adapter = useMemo(() => {
return isFarsi ? AdapterDateFnsJalali : AdapterDateFns;
}, [isFarsi]);
return (
<LocalizationProvider dateAdapter={Adapter}>
<DatePicker
label={t('completion.dateOfBirth')}
value={birthDate}
onChange={(newValue) => setBirthDate(newValue)}
slotProps={{
textField: {
fullWidth: true,
sx: {
width: {
xs: '100%',
sm: '70%',
md: '309px',
},
},
},
}}
/>
</LocalizationProvider>
);
}

View File

@@ -56,27 +56,42 @@ export function EmailSection({
return (
<>
<FormGroup>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, px: 6 }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
px: { xs: 2, sm: 6 },
}}
>
<Switch checked={showEmail} onChange={handleToggleEmail} />
<Typography sx={{ color: showEmail ? '#2979FF' : 'black' }}>
<Typography
sx={{ color: showEmail ? 'primary.main' : 'text.primary' }}
>
{t('completion.determineEmail')}
</Typography>
</Box>
</FormGroup>
{showEmail && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, px: 6 }}>
<Box sx={{ display: 'flex', gap: 4 }}>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
px: { xs: 2, sm: 6 },
}}
>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<Box sx={{ flex: 1, minWidth: '260px', maxWidth: '634px' }}>
<TextField
label={t('completion.email')}
variant="outlined"
fullWidth
value={email}
onChange={(e) => setEmail(e.target.value)}
error={!correctEmail}
sx={{
width: !isVerifyingCode && !emailVerified ? '446px' : '634px',
transition: 'width 0.3s',
}}
error={email.length > 0 && !correctEmail}
sx={{ maxWidth: '462px' }}
InputProps={{
startAdornment:
!isVerifyingCode && emailVerified ? (
@@ -114,46 +129,54 @@ export function EmailSection({
/>
{email && (
<Typography
sx={{ color: correctEmail ? 'green' : 'red' }}
sx={{ color: correctEmail ? 'success.main' : 'error.main' }}
variant="caption"
>
{correctEmail ? '' : t('completion.emailCorrectForm')}
</Typography>
)}
</Box>
{!isVerifyingCode && !emailVerified && (
<Button
variant="text"
onClick={handleSendCode}
sx={{ width: '156px' }}
// disabled={
// buttonState === 'sent' ||
// buttonState === 'counting' ||
// !correctEmail
// }
sx={{
minWidth: '140px',
width: { xs: '100%', sm: '156px' },
alignSelf: 'center',
}}
>
{getButtonLabel()}
</Button>
)}
</Box>
{!emailVerified && codeSent && (
<Box sx={{ display: 'flex', gap: 4 }}>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 2,
}}
>
<TextField
label={t('completion.verificationCode')}
variant="outlined"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
sx={{ width: '446px' }}
fullWidth={false}
sx={{ flex: 1 }}
disabled={isVerifyingCode}
/>
<Button
variant="contained"
sx={{
width: '156px',
border: 0.5,
}}
onClick={handleVerifyCode}
disabled={isVerifyingCode}
sx={{
width: { xs: '100%', sm: '156px' },
border: 0.5,
}}
>
{isVerifyingCode ? (
<Box

View File

@@ -54,26 +54,29 @@ export function PasswordSection({
setShowPasswordSection(e.target.checked);
};
const handleTogglePasswordEye = () => {
setShowPasswordText((prev) => !prev);
};
const handleTogglePasswordRepetitionEye = () => {
const handleTogglePasswordEye = () => setShowPasswordText((prev) => !prev);
const handleTogglePasswordRepetitionEye = () =>
setShowPasswordRepetitionText((prev) => !prev);
};
return (
<>
<FormGroup>
<Box sx={{ display: 'flex', gap: 0.5, px: 6, alignItems: 'center' }}>
<Box dir="ltr">
<Switch
checked={showPasswordSection}
onChange={handleTogglePasswordSection}
/>
</Box>
<Box
sx={{
display: 'flex',
gap: 0.5,
px: { xs: 2, sm: 6 },
alignItems: 'center',
}}
>
<Switch
checked={showPasswordSection}
onChange={handleTogglePasswordSection}
/>
<Typography
sx={{ color: showPasswordSection ? '#2979FF' : 'text.primary' }}
sx={{
color: showPasswordSection ? 'primary.main' : 'text.primary',
}}
>
{t('completion.determinePassword')}
</Typography>
@@ -81,15 +84,23 @@ export function PasswordSection({
</FormGroup>
{showPasswordSection && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, px: 6 }}>
<Box sx={{ display: 'flex', gap: 2 }}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
px: { xs: 2, sm: 6 },
}}
>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<TextField
label={t('completion.password')}
value={password}
onChange={(e) => setPassword(e.target.value)}
variant="outlined"
type={showPasswordText ? 'text' : 'password'}
sx={{ width: '309px' }}
fullWidth
sx={{ maxWidth: '309px', flex: 1 }}
InputProps={{
endAdornment: (
<InputAdornment position="end" sx={{ height: 'unset' }}>
@@ -144,7 +155,8 @@ export function PasswordSection({
? t('completion.notCompatibility')
: ' '
}
sx={{ width: '309px' }}
fullWidth
sx={{ maxWidth: '309px', flex: 1 }}
type={showPasswordRepititonText ? 'text' : 'password'}
InputProps={{
endAdornment: (
@@ -156,8 +168,8 @@ export function PasswordSection({
width: '100%',
}}
>
{confirmPassword.length > 0 ? (
matchPassword ? (
{confirmPassword.length > 0 &&
(matchPassword ? (
<TickCircle
size="24"
color="#14AE5C"
@@ -179,8 +191,7 @@ export function PasswordSection({
marginRight: '16px',
}}
/>
)
) : null}
))}
<IconButton
onClick={handleTogglePasswordRepetitionEye}
@@ -213,10 +224,12 @@ export function PasswordSection({
</Box>
{password && showValidations && (
<Box sx={{ display: 'flex', gap: 2 }}>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<Box
sx={{
width: '317px',
flex: 1,
minWidth: '260px',
maxWidth: '317px',
display: 'flex',
flexDirection: 'column',
}}
@@ -232,7 +245,9 @@ export function PasswordSection({
</Box>
<Box
sx={{
width: '317px',
flex: 1,
minWidth: '260px',
maxWidth: '317px',
display: 'flex',
flexDirection: 'column',
}}

View File

@@ -11,13 +11,29 @@ export function PasswordValidationItem({
label,
}: ValidationItemProps) {
return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
mb: 0.5,
flexWrap: 'wrap',
}}
>
<TickCircle
size="16"
color={isValid ? '#14AE5C' : '#2979FF'}
variant={isValid ? 'Bold' : 'Outline'}
/>
<Typography variant="body2" color="text.primary">
<Typography
variant="body2"
color="text.primary"
sx={{
fontSize: { xs: '0.85rem', sm: '0.875rem' },
wordBreak: 'break-word',
flex: 1,
}}
>
{label}
</Typography>
</Box>

View File

@@ -10,6 +10,8 @@ import {
import { useTranslation } from 'react-i18next';
import { Woman, Man } from 'iconsax-react';
import { useState } from 'react';
import { DateOfBirth } from './DateOfBirth';
import { countries } from '../data/Countries';
interface PersonalInfoFieldsProps {
sex: string;
@@ -27,39 +29,6 @@ export function PersonalInfoFields({
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: 'Argentina', fa: 'آرژانتین', flag: 'ar' },
{ name: 'Armenia', fa: 'ارمنستان', flag: 'am' },
{ name: 'Australia', fa: 'استرالیا', flag: 'au' },
{ name: 'Austria', fa: 'اتریش', flag: 'at' },
{ name: 'Bahrain', fa: 'بحرین', flag: 'bh' },
{ name: 'Canada', fa: 'کانادا', flag: 'ca' },
{ name: 'China', fa: 'چین', flag: 'cn' },
{ name: 'France', fa: 'فرانسه', flag: 'fr' },
{ name: 'Germany', fa: 'آلمان', flag: 'de' },
{ name: 'India', fa: 'هند', flag: 'in' },
{ name: 'Iran', fa: 'ایران', flag: 'ir' },
{ name: 'Iraq', fa: 'عراق', flag: 'iq' },
{ name: 'Italy', fa: 'ایتالیا', flag: 'it' },
{ name: 'Japan', fa: 'ژاپن', flag: 'jp' },
{ name: 'Netherlands', fa: 'هلند', flag: 'nl' },
{ name: 'Pakistan', fa: 'پاکستان', flag: 'pk' },
{ 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: 'Turkey', fa: 'ترکیه', flag: 'tr' },
{ 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 handleChangeSex = (e: any) => {
setSex(e.target.value);
};
@@ -68,43 +37,46 @@ export function PersonalInfoFields({
setCountry(newValue ? newValue.name : '');
setCountryError(false);
};
const handleCountryBlur = (e: React.FocusEvent<HTMLInputElement>) => {
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);
}
setCountryError(Boolean(inputValue && !exists));
};
const handleInputChange = (_: any, value: string) => {
const exists = countries.some((c) => c.fa === value.trim());
setCountryError(Boolean(value.trim() && !exists));
};
const fieldSx = { width: { xs: '100%', sm: '48%', md: '48%' } };
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, px: 6 }}>
<Box sx={{ display: 'flex', gap: 2 }}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
px: { xs: 2, sm: 4, md: 6 },
}}
>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<TextField
label={t('completion.name')}
placeholder={t('completion.name')}
variant="outlined"
sx={{ width: '309px' }}
sx={fieldSx}
/>
<TextField
label={t('completion.familyName')}
placeholder={t('completion.familyName')}
variant="outlined"
sx={{ width: '309px' }}
sx={fieldSx}
/>
</Box>
<Box sx={{ display: 'flex', gap: 2 }}>
<FormControl sx={{ width: '309px' }}>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<FormControl sx={fieldSx}>
<InputLabel>{t('completion.gender')}</InputLabel>
<Select
value={sex}
@@ -130,11 +102,11 @@ export function PersonalInfoFields({
label={t('completion.optionalNationalCode')}
placeholder={t('completion.optionalNationalCode')}
variant="outlined"
sx={{ width: '309px' }}
sx={fieldSx}
/>
</Box>
<Box sx={{ display: 'flex', gap: 2 }}>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<Autocomplete
options={countries}
getOptionLabel={(c) => (typeof c === 'string' ? c : c.fa)}
@@ -169,15 +141,12 @@ export function PersonalInfoFields({
onBlur={handleCountryBlur}
/>
)}
sx={{ width: '309px' }}
sx={fieldSx}
/>
<TextField
label={t('completion.dateOfBirth')}
placeholder={t('completion.dateOfBirth')}
variant="outlined"
sx={{ width: '309px' }}
/>
<Box sx={fieldSx}>
<DateOfBirth />
</Box>
</Box>
</Box>
);

View File

@@ -5,13 +5,30 @@ export function SubmitSection() {
const { t } = useTranslation('completionForm');
return (
<Box sx={{ display: 'flex', gap: 2, px: 6, mb: '17px' }}>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
alignItems: { xs: 'stretch', sm: 'center' },
gap: 2,
px: { xs: 2, sm: 4, md: 6 },
mb: '17px',
}}
>
<Typography variant="body2" sx={{ flex: 1 }} color="text.primary">
{t('completion.agreementPart1')}
<Link target="_blank">{t('completion.agreementLinkText')}</Link>{' '}
<Link target="_blank" rel="noopener" href="#">
{t('completion.agreementLinkText')}
</Link>{' '}
{t('completion.agreementPart2')}
</Typography>
<Button variant="contained" sx={{ width: '247px' }}>
<Button
variant="contained"
sx={{
width: { xs: '100%', sm: '247px' },
alignSelf: { xs: 'stretch', sm: 'auto' },
}}
>
{t('completion.registerButton')}
</Button>
</Box>

View File

@@ -19,7 +19,7 @@ export function UserCompletionForm() {
const [verificationCode, setVerificationCode] = useState('');
const [buttonState, setButtonState] = useState<
'default' | 'counting' | 'sent'
>('default'); // default | counting | sent
>('default');
const [countdown, setCountdown] = useState(60);
const [emailVerified, setEmailVerified] = useState(false);
const [isVerifyingCode, setIsVerifyingCode] = useState(false);
@@ -32,7 +32,6 @@ export function UserCompletionForm() {
const validPassword =
hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar;
const [showPasswordValidations, setShowPasswordValidations] = useState(false);
const correctEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
useEffect(() => {
@@ -40,9 +39,7 @@ export function UserCompletionForm() {
if (!validPassword) {
setShowPasswordValidations(true);
} else {
const timer = setTimeout(() => {
setShowPasswordValidations(false);
}, 1000);
const timer = setTimeout(() => setShowPasswordValidations(false), 1000);
return () => clearTimeout(timer);
}
} else {
@@ -71,8 +68,7 @@ export function UserCompletionForm() {
if (buttonState === 'counting') {
const minutes = String(Math.floor(countdown / 60)).padStart(2, '0');
const seconds = String(countdown % 60).padStart(2, '0');
const time = `${minutes}:${seconds}`;
return toPersianDigits(time);
return toPersianDigits(`${minutes}:${seconds}`);
}
return t('completion.vericationCodeButton');
};
@@ -103,41 +99,38 @@ export function UserCompletionForm() {
return (
<Box
sx={{
backgroundColor: '#F5F5F5',
backgroundColor: 'background.default',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
p: 2,
p: { xs: 1, sm: 2, md: 3 },
}}
>
<Box
sx={{
width: '730px',
backgroundColor: '#FFFFFF',
width: '100%',
maxWidth: '730px',
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',
px: 4,
width: '100%',
maxWidth: '666px',
height: '92px',
mt: '32px',
gap: 1,
}}
>
<Typography
variant="h5"
color="text.primary"
sx={{ mt: '16px', px: 2 }}
>
<Typography variant="h5" color="text.primary">
{t('completion.title')}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ px: 2 }}>
<Typography variant="body2" color="text.secondary">
{t('completion.description')}
</Typography>
</Box>

View File

@@ -0,0 +1,33 @@
// components/CountryList.ts
export const countries = [
{ name: 'Afghanistan', fa: 'افغانستان', flag: 'af' },
{ name: 'Albania', fa: 'آلبانی', flag: 'al' },
{ name: 'Algeria', fa: 'الجزایر', flag: 'dz' },
{ name: 'Argentina', fa: 'آرژانتین', flag: 'ar' },
{ name: 'Armenia', fa: 'ارمنستان', flag: 'am' },
{ name: 'Australia', fa: 'استرالیا', flag: 'au' },
{ name: 'Austria', fa: 'اتریش', flag: 'at' },
{ name: 'Bahrain', fa: 'بحرین', flag: 'bh' },
{ name: 'Canada', fa: 'کانادا', flag: 'ca' },
{ name: 'China', fa: 'چین', flag: 'cn' },
{ name: 'France', fa: 'فرانسه', flag: 'fr' },
{ name: 'Germany', fa: 'آلمان', flag: 'de' },
{ name: 'India', fa: 'هند', flag: 'in' },
{ name: 'Iran', fa: 'ایران', flag: 'ir' },
{ name: 'Iraq', fa: 'عراق', flag: 'iq' },
{ name: 'Italy', fa: 'ایتالیا', flag: 'it' },
{ name: 'Japan', fa: 'ژاپن', flag: 'jp' },
{ name: 'Netherlands', fa: 'هلند', flag: 'nl' },
{ name: 'Pakistan', fa: 'پاکستان', flag: 'pk' },
{ 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: 'Turkey', fa: 'ترکیه', flag: 'tr' },
{ name: 'United Arab Emirates', fa: 'امارات متحده عربی', flag: 'ae' },
{ name: 'United Kingdom', fa: 'بریتانیا', flag: 'gb' },
{ name: 'United States', fa: 'ایالات متحده آمریکا', flag: 'us' },
{ name: 'Yemen', fa: 'یمن', flag: 'ye' },
];