fix: styles and add a country textfield in personal field

This commit is contained in:
2025-07-23 17:28:25 +03:30
parent 3b3db1be41
commit 9c9e148426
7 changed files with 381 additions and 327 deletions

View File

@@ -31,6 +31,11 @@
"newEmail": "ایمیل جدید",
"dialogHeader": "با فعال‌سازی ایمیل می‌توانید در دفعات بعدی ورود برای ورود از این ایمیل استفاده کنید",
"or": "یا",
"emailError": "لطفا یک ایمیل معتبر وارد کنید"
"emailError": "لطفا یک ایمیل معتبر وارد کنید",
"profilePicture": "تصویر حساب کاربری",
"allowedFormat": "فرمت‌های مجاز: PNG، JPEG، GIF (حداکثر ۱۰ مگابایت)",
"uploadPicture": "بارگذاری تصویر",
"phoneNumberText": "شماره تماس جدید شما جایگزین شماره تماس قبلی",
"verb": "خواهد شد"
}
}

View File

@@ -14,6 +14,7 @@ import { LanguageManager } from './components/LanguageManager';
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> f1620b6 (fix: issue in user profile)
@@ -30,6 +31,9 @@ import { UserForm } from './features/profile/components/UserForm';
>>>>>>> 60c6dc1 (fix: styles)
=======
>>>>>>> afc7ff8 (fix: translation and styles)
=======
import { UserForm } from './features/profile/components/UserForm';
>>>>>>> b327e7a (fix: styles and add a country textfield in personal field)
function App() {
const { t } = useTranslation();
@@ -37,6 +41,7 @@ function App() {
<>
<CssBaseline />
<LanguageManager />
<UserForm />
<div style={{ padding: '16px' }}>
<Typography variant="h3">{t('helloWorld')}</Typography>
<Box

View File

@@ -15,12 +15,14 @@ export function CardContainer({
return (
<Box
sx={{
width: '754px',
maxWidth: '786px',
width: '100%',
backgroundColor: 'white',
// boxShadow: 2,
display: 'flex',
flexDirection: 'column',
gap: 2,
px: 2,
py: 2,
}}
>
<Box

View File

@@ -1,6 +1,6 @@
import { Box, Typography } from '@mui/material';
const countryCodeMap: { [key: string]: string } = {
export const countryCodeMap: { [key: string]: string } = {
ایران: 'ir',
قطر: 'qa',
آلمان: 'de',

View File

@@ -6,18 +6,21 @@ import {
FormControl,
Select,
MenuItem,
Avatar,
Autocomplete,
type SelectChangeEvent,
} from '@mui/material';
import { useState, type ChangeEvent } from 'react';
import { CardContainer } from '@/components/CardContainer';
import { CountryFlag } from '@/components/CountryFlag';
import { CountryFlag, countryCodeMap } from '@/components/CountryFlag';
import { useTranslation } from 'react-i18next';
import { Profile } from 'iconsax-react';
import { Camera } from 'iconsax-react';
export function PersonalInformation() {
const { t } = useTranslation('profileSetting');
const [isEditing, setIsEditing] = useState(false);
const [gender, setGender] = useState('');
const initialData = {
firstName: 'محمد حسین',
lastName: 'برزه‌گر',
@@ -26,6 +29,8 @@ export function PersonalInformation() {
nationalCode: '',
};
const [data, setData] = useState(initialData);
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setData((prev) => ({
@@ -56,16 +61,22 @@ export function PersonalInformation() {
return value && value.trim() !== '' ? value : 'تعیین نشده';
};
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
setUploadedImageUrl(reader.result as string);
};
reader.readAsDataURL(file);
}
};
const initials = `${data.firstName?.trim()[0] || ''}${data.lastName?.trim()[0] || ''}`;
return (
<Box
sx={{
display: 'flex',
backgroundColor: '#F5F5F5',
justifyContent: 'center',
alignItems: 'center',
p: 2,
overflow: 'hidden',
}}
sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}
>
<CardContainer
title={t('settingForm.titlePersonalInfo')}
@@ -78,10 +89,7 @@ export function PersonalInformation() {
variant="text"
onClick={() => setIsEditing(false)}
size="large"
sx={{
color: '#2979FF',
width: '43px',
}}
sx={{ color: '#2979FF', width: '43px' }}
>
{t('settingForm.rejectButton')}
</Button>
@@ -109,142 +117,178 @@ export function PersonalInformation() {
>
<Box
sx={{
px: 4,
py: 2,
display: 'flex',
flexWrap: 'wrap',
gap: 4,
p: 2,
flexDirection: 'column',
gap: 2,
}}
>
<Box sx={{ width: 'calc(50% - 16px)' }}>
{isEditing ? (
<TextField
fullWidth
name="firstName"
value={data.firstName}
onChange={handleChange}
label={t('settingForm.name')}
/>
) : (
<Box
{isEditing && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Avatar
sx={{
height: 'auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'flex-start',
gap: 0.5,
px: 4,
bgcolor: '#00C3B7',
width: 88,
height: 88,
fontSize: '20px',
}}
src={uploadedImageUrl || undefined}
>
<Typography variant="caption" color="text.secondary">
{`${t('settingForm.name')} و ${t('settingForm.familyName')}`}
{initials}
</Avatar>
<Box>
<Typography variant="body1">
{t('settingForm.profilePicture')}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Profile size="20" color="gray" />
<Typography variant="body1" color="text.primary">
{displayValue(data.firstName + ' ' + data.lastName)}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('settingForm.allowedFormat')}
</Typography>
<Box mt={1}>
<Button
variant="contained"
component="label"
sx={{
borderRadius: 2,
textTransform: 'none',
height: '30px',
fontSize: '13px',
}}
startIcon={<Camera size={18} color="white" />}
>
{t('settingForm.uploadPicture')}
<input
hidden
accept="image/png, image/jpeg, image/gif"
type="file"
onChange={handleImageChange}
/>
</Button>
</Box>
</Box>
)}
</Box>
</Box>
)}
<Box sx={{ width: 'calc(50% - 16px)' }}>
{isEditing ? (
<TextField
fullWidth
name="lastName"
value={data.lastName}
onChange={handleChange}
label={t('settingForm.familyName')}
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<Box sx={{ width: 'calc(50% - 8px)' }}>
{isEditing ? (
<TextField
fullWidth
name="firstName"
value={data.firstName}
onChange={handleChange}
label={t('settingForm.name')}
/>
) : (
<Box sx={{ px: 4 }}>
<Typography variant="caption" color="text.secondary">
{`${t('settingForm.name')} و ${t('settingForm.familyName')}`}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Avatar>{initials}</Avatar>
<Typography variant="body1" color="text.primary">
{displayValue(data.firstName + ' ' + data.lastName)}
</Typography>
</Box>
</Box>
)}
</Box>
<Box sx={{ width: 'calc(50% - 8px)' }}>
{isEditing ? (
<TextField
fullWidth
name="lastName"
value={data.lastName}
onChange={handleChange}
label={t('settingForm.familyName')}
/>
) : (
<Box sx={{ px: 4 }}>
<Typography variant="caption" color="text.secondary">
{t('settingForm.country')}
</Typography>
<CountryFlag country={data.country} />
</Box>
)}
</Box>
<Box sx={{ width: 'calc(50% - 8px)' }}>
{isEditing ? (
<FormControl fullWidth>
<Select
value={gender}
onChange={handleChangeGender}
displayEmpty
renderValue={(selected) => {
if (!selected) {
return (
<span style={{ color: '#aaa' }}>
{t('settingForm.genderPlaceholder')}
</span>
);
}
return selected === 'male'
? t('settingForm.man')
: t('settingForm.woman');
}}
>
<MenuItem value="male">{t('settingForm.man')}</MenuItem>
<MenuItem value="female">{t('settingForm.woman')}</MenuItem>
</Select>
</FormControl>
) : (
<Box sx={{ px: 4 }}>
<Typography variant="caption" color="text.secondary">
{t('settingForm.gender')}
</Typography>
<Typography variant="body1" color="text.primary">
{displayValue(data.gender)}
</Typography>
</Box>
)}
</Box>
<Box sx={{ width: 'calc(50% - 8px)' }}>
{isEditing ? (
<TextField
fullWidth
name="nationalCode"
value={data.nationalCode}
onChange={handleChange}
label={t('settingForm.nationalCode')}
/>
) : (
<Box sx={{ px: 4 }}>
<Typography variant="caption" color="text.secondary">
{t('settingForm.nationalCode')}
</Typography>
<Typography variant="body1" color="text.primary">
{displayValue(data.nationalCode)}
</Typography>
</Box>
)}
</Box>
{isEditing && (
<Autocomplete
sx={{ width: 'calc(50% - 8px)' }}
options={Object.keys(countryCodeMap)}
value={data.country}
onChange={(_, newValue) =>
setData((prev) => ({
...prev,
country: newValue || '',
}))
}
renderOption={(props, option) => (
<Box component="li" {...props}>
<CountryFlag country={option} />
</Box>
)}
renderInput={(params) => (
<TextField {...params} label={t('settingForm.country')} />
)}
/>
) : (
<Box
sx={{
height: '48px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
>
<Typography variant="caption" color="text.secondary">
{t('settingForm.country')}
</Typography>
<CountryFlag country={data.country} />
</Box>
)}
</Box>
<Box sx={{ width: 'calc(50% - 16px)' }}>
{isEditing ? (
<FormControl fullWidth>
<Select
value={gender}
onChange={handleChangeGender}
displayEmpty
renderValue={(selected) => {
if (!selected) {
return (
<span style={{ color: '#aaa' }}>
{t('settingForm.genderPlaceholder')}
</span>
);
}
return selected === 'male'
? t('settingForm.man')
: t('settingForm.woman');
}}
>
<MenuItem value="male">{t('settingForm.man')}</MenuItem>
<MenuItem value="female">{t('settingForm.woman')}</MenuItem>
</Select>
</FormControl>
) : (
<Box
sx={{
height: '48px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
px: 4,
}}
>
<Typography variant="caption" color="text.secondary">
{t('settingForm.gender')}
</Typography>
<Typography variant="body1" color="text.primary">
{displayValue(data.gender)}
</Typography>
</Box>
)}
</Box>
<Box sx={{ width: 'calc(50% - 16px)' }}>
{isEditing ? (
<TextField
fullWidth
name="nationalCode"
value={data.nationalCode}
onChange={handleChange}
label={t('settingForm.nationalCode')}
/>
) : (
<Box
sx={{
height: '48px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
>
<Typography variant="caption" color="text.secondary">
{t('settingForm.nationalCode')}{' '}
</Typography>
<Typography variant="body1" color="text.primary">
{displayValue(data.nationalCode)}
</Typography>
</Box>
)}
</Box>
</Box>

View File

@@ -23,7 +23,9 @@ export function PhoneNumber() {
const [isVerifying, setIsVerifying] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const phones = [{ phone: '09123456789', time: '۱ ماه پیش' }];
const phones = [
{ phone: '09123456789', time: '۱ ماه پیش', withCode: '+989123456789' },
];
const handleSendCode = () => {
setButtonState('counting');
@@ -56,210 +58,206 @@ export function PhoneNumber() {
};
return (
<>
<GlobalStyles
styles={{
'@keyframes spin': {
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' },
},
}}
/>
<Box
sx={{
display: 'flex',
backgroundColor: '#F5F5F5',
justifyContent: 'center',
alignItems: 'center',
p: 2,
}}
>
<CardContainer
title={t('settingForm.titlePhoneNumber')}
subtitle={t('settingForm.descriptionPhoneNumber')}
highlighted={isEditing}
action={
<Box sx={{ display: 'flex', gap: 1 }}>
{isEditing && (
<Button
variant="text"
onClick={toggleEdit}
size="large"
sx={{ color: '#2979FF', width: '43px' }}
>
{t('settingForm.rejectButton')}
</Button>
)}
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
// p: 2,
}}
>
<CardContainer
title={t('settingForm.titlePhoneNumber')}
subtitle={t('settingForm.descriptionPhoneNumber')}
highlighted={isEditing}
action={
<Box sx={{ display: 'flex', gap: 1 }}>
{isEditing && (
<Button
variant="text"
onClick={toggleEdit}
size="large"
variant="outlined"
sx={{
border: '1px solid',
borderColor: '#2979FF',
borderRadius: '4px',
backgroundColor: isEditing ? '#2979FF' : 'white',
color: isEditing ? 'white' : '#2979FF',
width: isEditing ? '85px' : '161px',
}}
sx={{ color: '#2979FF', width: '43px' }}
>
{isEditing
? t('settingForm.saveButton')
: t('settingForm.editPhoneNumber')}
{t('settingForm.rejectButton')}
</Button>
)}
<Button
onClick={toggleEdit}
size="large"
variant="outlined"
sx={{
border: '1px solid',
borderColor: '#2979FF',
borderRadius: '4px',
backgroundColor: isEditing ? '#2979FF' : 'white',
color: isEditing ? 'white' : '#2979FF',
width: isEditing ? '85px' : '161px',
}}
>
{isEditing
? t('settingForm.saveButton')
: t('settingForm.editPhoneNumber')}
</Button>
</Box>
}
>
{isEditing ? (
<Box sx={{ px: 4 }}>
<Box sx={{ width: '668px', mb: 2 }}>
<Typography variant="h6" color="text.primary">
{t('settingForm.editPhoneNumber')}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('settingForm.phoneNumberText')}(
{phones.map((p) => p.withCode)}){t('settingForm.verb')}
</Typography>
</Box>
}
>
{isEditing ? (
<Box sx={{ px: 4 }}>
<Box sx={{ width: '668px', mb: 2 }}>
<Typography variant="h6" color="text.primary">
{t('settingForm.editPhoneNumber')}
</Typography>
<Typography variant="body2" color="text.secondary">
شماره تماس جدید شما جایگزین شماره تماس قبلی(+989123456789)
خواهد شد.
</Typography>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
width: '700px',
gap: 2,
}}
>
<TextField
name="phoneNumber"
placeholder="09123456789"
label={t('settingForm.newPhoneNumber')}
type="tel"
value={phoneNumber}
onChange={handlePhoneNumberChange}
sx={{ width: '100%', maxWidth: '474px' }}
inputProps={{
dir: 'rtl',
style: { textAlign: 'right' },
}}
InputProps={{
endAdornment:
buttonState === 'counting' ? (
<IconButton
size="small"
onClick={() => setButtonState('default')}
sx={{ mr: 1 }}
>
<Edit size="24" color="#2979FF" />
</IconButton>
) : null,
}}
/>
{isVerified ? (
<Box
sx={{
width: '100%',
maxWidth: '194px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'success.main',
gap: 1,
}}
>
<TickCircle size="20" color="#2e7d32" variant="Bold" />
{t('settingForm.successButton')}
</Box>
) : (
<Button
variant="text"
sx={{ width: '100%', maxWidth: '194px', color: '#2979FF' }}
size="large"
onClick={handleSendCode}
disabled={buttonState === 'counting'}
>
{buttonState === 'counting' ? (
<CountDownTimer
initialSeconds={60}
onComplete={() => setButtonState('default')}
/>
) : (
t('settingForm.verificationCodeButton')
)}
</Button>
)}
</Box>
{buttonState === 'counting' && !isVerified && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
width: '700px',
gap: 2,
mt: 2,
}}
>
<TextField
name="phoneNumber"
placeholder="09123456789"
label={t('settingForm.newPhoneNumber')}
name="verificationCode"
placeholder={t('settingForm.verificationCode')}
label={t('settingForm.verificationCode')}
type="tel"
value={phoneNumber}
onChange={handlePhoneNumberChange}
value={verificationCode}
onChange={handleVerificationCodeChange}
sx={{ width: '100%', maxWidth: '474px' }}
InputProps={{
startAdornment:
buttonState === 'counting' ? (
<IconButton
size="small"
onClick={() => setButtonState('default')}
>
<Edit size="20" color="#2979FF" />
</IconButton>
) : null,
}}
/>
{isVerified ? (
<Box
sx={{
width: '100%',
maxWidth: '194px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'success.main',
gap: 1,
}}
>
<TickCircle size="20" color="#2e7d32" variant="Bold" />
{t('profileSetting.successButton')}
</Box>
) : (
<Button
variant="text"
sx={{ width: '100%', maxWidth: '194px', color: '#2979FF' }}
size="large"
onClick={handleSendCode}
disabled={buttonState === 'counting'}
>
{buttonState === 'counting' ? (
<CountDownTimer
initialSeconds={60}
onComplete={() => setButtonState('default')}
/>
) : (
t('settingForm.verificationCodeButton')
)}
</Button>
)}
</Box>
{buttonState === 'counting' && !isVerified && (
<Box
<Button
variant="contained"
size="large"
onClick={handleVerifyCode}
disabled={isVerifying || verificationCode.length === 0}
sx={{
width: '100%',
maxWidth: '194px',
display: 'flex',
alignItems: 'center',
width: '700px',
gap: 2,
mt: 2,
}}
>
<TextField
name="verificationCode"
placeholder={t('settingForm.verificationCode')}
label={t('settingForm.verificationCode')}
type="tel"
value={verificationCode}
onChange={handleVerificationCodeChange}
sx={{ width: '100%', maxWidth: '474px' }}
/>
<Button
variant="contained"
size="large"
onClick={handleVerifyCode}
disabled={isVerifying || verificationCode.length === 0}
sx={{
width: '100%',
maxWidth: '194px',
display: 'flex',
justifyContent: 'center',
backgroundColor: '#2979FF',
}}
>
{isVerifying ? (
<Box
component="span"
sx={{
display: 'flex',
animation: 'spin 1s linear infinite',
}}
>
<Refresh size="20" color="white" />
</Box>
) : (
t('settingForm.checkCode')
)}
</Button>
</Box>
)}
</Box>
) : (
<Box sx={{ px: 4 }}>
{phones.map((item, index) => (
<Box
key={index}
sx={{
height: '48px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
mb: 1,
backgroundColor: '#2979FF',
}}
>
<Typography variant="h6" color="text.primary">
{item.phone}
</Typography>
<Typography variant="caption" color="text.secondary">
{item.time}
</Typography>
</Box>
))}
</Box>
)}
</CardContainer>
</Box>
</>
{isVerifying ? (
<Box
component="span"
sx={{
display: 'flex',
animation: 'spin 1s linear infinite',
}}
>
<Refresh size="20" color="white" />
</Box>
) : (
t('settingForm.checkCode')
)}
</Button>
</Box>
)}
</Box>
) : (
<Box sx={{ px: 4 }}>
{phones.map((item, index) => (
<Box
key={index}
sx={{
height: '76px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
// mb: 1,
width: '690px',
// mt: 1,
}}
>
<Typography variant="h6" color="text.primary">
{item.phone}
</Typography>
<Typography variant="caption" color="text.secondary">
{item.time}
</Typography>
</Box>
))}
</Box>
)}
</CardContainer>
</Box>
);
}

View File

@@ -54,10 +54,10 @@ export function SocialMedia() {
return (
<Box
sx={{
backgroundColor: '#F5F5F5',
// backgroundColor: '#F5F5F5',
display: 'flex',
justifyContent: 'center',
p: 2,
// p: 2,
}}
>
<CardContainer