Merge branch 'feat/user-profile' into feat/user-security

This commit is contained in:
Koosha Lahouti
2025-08-02 12:50:34 +03:30
committed by GitHub
17 changed files with 1597 additions and 8 deletions

View File

@@ -0,0 +1,129 @@
import React, { useState } from 'react';
import { Box, Button } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer';
import { ProfileImage } from './personlInformation/ProfileImage';
import { InfoRowDisplay } from './personlInformation/InfoRowDisplay';
import { InfoRowEdit } from './personlInformation/InfoRowEdit';
export function PersonalInformation() {
const { t } = useTranslation('profileSetting');
const [isEditing, setIsEditing] = useState(false);
const [gender, setGender] = useState('');
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
const initialData = {
firstName: 'محمد حسین',
lastName: 'برزه‌گر',
country: 'قطر',
gender: 'مرد',
nationalCode: '',
};
const [data, setData] = useState(initialData);
const initials = `${data.firstName?.trim()[0] || ''}${data.lastName?.trim()[0] || ''}`;
const toggleEdit = () => {
setIsEditing((prev) => !prev);
if (isEditing) {
setData((prev) => ({
...prev,
gender: gender === 'male' ? 'مرد' : gender === 'female' ? 'زن' : '',
}));
} else {
setGender(
data.gender === 'مرد' ? 'male' : data.gender === 'زن' ? 'female' : '',
);
}
};
return (
<CardContainer
title={t('settingForm.titlePersonalInfo')}
subtitle={t('settingForm.descriptionPersonalInfo')}
highlighted={isEditing}
action={
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{isEditing && (
<Button
variant="text"
onClick={() => setIsEditing(false)}
size="large"
sx={{
color: 'primary.main',
textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
fontSize: { xs: '0.85rem', sm: '1rem' },
}}
>
{t('settingForm.rejectButton')}
</Button>
)}
<Button
onClick={toggleEdit}
size="large"
variant="outlined"
sx={{
textTransform: 'none',
border: '1px solid',
borderColor: 'primary.main',
borderRadius: '4px',
bgcolor: isEditing ? 'primary.main' : 'background.paper',
color: isEditing ? 'primary.contrastText' : 'primary.main',
px: { xs: 2, sm: '22px' },
py: { xs: '6px', sm: '8px' },
width: { xs: '100%', sm: isEditing ? '85px' : '93px' },
fontSize: { xs: '0.9rem', sm: '1rem' },
}}
>
{isEditing
? t('settingForm.saveButton')
: t('settingForm.editButton')}
</Button>
</Box>
}
>
<Box
sx={{
px: { xs: 2, sm: 3, md: 4 },
py: 2,
display: 'flex',
flexDirection: 'column',
gap: 2,
bgcolor: 'background.paper',
}}
>
{isEditing && (
<ProfileImage
initials={initials}
uploadedImageUrl={uploadedImageUrl}
onImageChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () =>
setUploadedImageUrl(reader.result as string);
reader.readAsDataURL(file);
}
}}
/>
)}
{isEditing ? (
<InfoRowEdit
data={data}
setData={setData}
gender={gender}
setGender={setGender}
/>
) : (
<InfoRowDisplay
data={data}
uploadedImageUrl={uploadedImageUrl}
initials={initials}
/>
)}
</Box>
</CardContainer>
);
}

View File

@@ -0,0 +1,286 @@
import React, { useState, type ChangeEvent } from 'react';
import { Box, Typography, Button, TextField, IconButton } from '@mui/material';
import { Edit, Refresh, TickCircle } from 'iconsax-react';
import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer';
import { CountDownTimer } from '@/components/CountDownTimer';
import { Toast } from '@/components/Toast';
export function PhoneNumber() {
const { t } = useTranslation('profileSetting');
const [isEditing, setIsEditing] = useState(false);
const [phoneNumber, setPhoneNumber] = useState('');
const [verificationCode, setVerificationCode] = useState('');
const [showToast, setShowToast] = useState(false);
const [buttonState, setButtonState] = useState<'default' | 'counting'>(
'default',
);
const [isVerifying, setIsVerifying] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const [phones, setPhone] = useState([
{ phone: '09123456789', time: '۱ ماه پیش', withCode: '+989123456789' },
]);
const toggleEdit = () => {
setIsEditing((prev) => {
const enteringEditMode = !prev;
if (enteringEditMode) {
setPhoneNumber('');
setVerificationCode('');
setIsVerified(false);
setButtonState('default');
setShowToast(false);
}
return enteringEditMode;
});
};
const handlePhoneNumberChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/\D/g, '');
setPhoneNumber(value);
};
const handleVerificationCodeChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/\D/g, '');
setVerificationCode(value);
};
const handleSendCode = () => {
if (!phoneNumber) return;
setButtonState('counting');
setIsVerified(false);
};
const handleVerifyCode = () => {
setIsVerifying(true);
setTimeout(() => {
setIsVerifying(false);
setIsVerified(true);
setShowToast(true);
const newPhone = '+98' + phoneNumber.slice(1);
setPhone([
{ phone: phoneNumber, time: 'چند ثانیه پیش', withCode: newPhone },
]);
}, 1500);
};
const handleVerifyClick = () => {
if (!verificationCode || isVerifying) return;
handleVerifyCode();
setTimeout(() => setShowToast(true), 1600);
};
return (
<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: 'primary.main',
textTransform: 'none',
width: '43px',
}}
>
{t('settingForm.rejectButton')}
</Button>
)}
<Button
onClick={toggleEdit}
size="large"
variant="outlined"
sx={{
textTransform: 'none',
border: '1px solid',
borderColor: 'primary.main',
borderRadius: '4px',
bgcolor: isEditing ? 'primary.main' : 'background.paper',
color: isEditing ? 'primary.contrastText' : 'primary.main',
width: { xs: '100%', sm: isEditing ? '85px' : '161px' },
whiteSpace: 'nowrap',
}}
>
{isEditing
? t('settingForm.saveButton')
: t('settingForm.editPhoneNumber')}
</Button>
</Box>
}
>
{isEditing ? (
<Box sx={{ px: { xs: 2, sm: 4 }, bgcolor: 'background.paper' }}>
<Box sx={{ mb: 2 }}>
<Typography variant="h6">
{t('settingForm.editPhoneNumber')}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('settingForm.phoneNumberText')}({phones.map((p) => p.withCode)}
){t('settingForm.verb')}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2,
alignItems: 'center',
}}
>
<TextField
name="phoneNumber"
label={t('settingForm.newPhoneNumber')}
type="tel"
value={phoneNumber}
onChange={handlePhoneNumberChange}
sx={{ flex: '1 1 240px', minWidth: 0 }}
placeholder="09123456789"
inputProps={{ dir: 'rtl', style: { textAlign: 'right' } }}
InputProps={{
endAdornment:
buttonState === 'counting' ? (
<IconButton
size="small"
onClick={() => {
setButtonState('default');
setPhoneNumber('');
}}
sx={{ mr: 1 }}
>
<Edit size="24" color="#64B5F6" />
</IconButton>
) : null,
}}
/>
{isVerified ? (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<TickCircle
size="24"
style={{ color: '#43A047' }}
variant="Bold"
/>
<Typography sx={{ color: 'success.main' }}>
{t('settingForm.successButton')}
</Typography>
</Box>
) : (
<Button
variant="text"
onClick={handleSendCode}
disabled={
buttonState === 'counting' || phoneNumber.length === 0
}
sx={{
textTransform: 'none',
minWidth: { xs: '100%', sm: 170 },
color: 'primary.main',
height: 56,
}}
>
{buttonState === 'counting' ? (
<CountDownTimer
initialSeconds={60}
onComplete={() => setButtonState('default')}
/>
) : (
t('settingForm.verificationCodeButton')
)}
</Button>
)}
</Box>
{buttonState === 'counting' && !isVerified && (
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2,
mt: 2,
alignItems: 'center',
}}
>
<TextField
name="verificationCode"
label={t('settingForm.verificationCode')}
type="tel"
value={verificationCode}
onChange={handleVerificationCodeChange}
sx={{ flex: '1 1 240px', minWidth: 0 }}
placeholder={t('settingForm.verificationCode')}
inputProps={{ dir: 'rtl', style: { textAlign: 'right' } }}
/>
<Button
variant="contained"
onClick={handleVerifyClick}
disabled={isVerifying || verificationCode.length === 0}
sx={{
textTransform: 'none',
minWidth: { xs: '100%', sm: 170 },
bgcolor: 'primary.main',
height: 56,
}}
>
{isVerifying ? (
<Box
component="span"
sx={{
display: 'flex',
animation: 'spin 1s linear infinite',
'@keyframes spin': {
'0%': { transform: 'rotate(0deg)' },
'100%': { transform: 'rotate(360deg)' },
},
}}
>
<Refresh size="20" color="white" />
</Box>
) : (
t('settingForm.checkCode')
)}
</Button>
</Box>
)}
<Toast
color="success"
open={showToast}
onClose={() => setShowToast(false)}
>
{t('settingForm.successfulChangePhone')}
</Toast>
</Box>
) : (
<Box sx={{ px: { xs: 2, sm: 4 } }}>
{phones.map((item, index) => (
<Box
key={index}
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
py: 2,
width: '100%',
}}
>
<Typography variant="h6" color="text.primary">
{item.phone}
</Typography>
<Typography variant="caption" color="text.secondary">
{item.time}
</Typography>
</Box>
))}
</Box>
)}
</CardContainer>
);
}

View File

@@ -0,0 +1,331 @@
import React, { useState } from 'react';
import {
Box,
Button,
Typography,
Dialog,
DialogTitle,
DialogContent,
IconButton,
TextField,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
useMediaQuery,
} from '@mui/material';
import type { Theme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer';
import {
Google,
Apple,
Sms,
Trash,
CloseSquare,
Message,
ArrowDown3,
} from 'iconsax-react';
export function SocialMedia() {
const { t } = useTranslation('profileSetting');
const [openDialog, setOpenDialog] = useState(false);
const [emailInput, setEmailInput] = useState('');
const [emailError, setEmailError] = useState(false);
const [anchor, setAnchor] = useState<null | HTMLElement>(null);
const openMenu = Boolean(anchor);
const fullScreen = useMediaQuery((theme: Theme) =>
theme.breakpoints.down('sm'),
);
const handleOpenDialog = () => setOpenDialog(true);
const handleCloseDialog = () => setOpenDialog(false);
const handleClickMenu = (e: React.MouseEvent<HTMLButtonElement>) =>
setAnchor(e.currentTarget);
const handleCloseMenu = () => setAnchor(null);
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmailInput(value);
setEmailError(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value));
};
const emailList = [
{ email: 'emailtemp@email.com', provider: 'email', time: '1 ماه پیش' },
{ email: 'emailtemp@gmail.com', provider: 'google', time: '1 ماه پیش' },
{ email: 'emailtemp@icloud.com', provider: 'apple', time: '1 ماه پیش' },
] as const;
return (
<CardContainer
title={t('settingForm.titleSocial')}
subtitle={t('settingForm.descriptionSocial')}
action={
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
flexWrap: 'wrap',
gap: 1,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
alignItems: { xs: 'stretch', sm: 'flex-start' },
gap: 1,
width: '100%',
}}
>
<Button
onClick={handleClickMenu}
variant="outlined"
sx={{
width: '100%',
maxWidth: { sm: 187 },
textTransform: 'none',
border: '1px solid',
borderColor: 'primary.main',
borderRadius: '8px',
color: 'primary.main',
fontSize: '14px',
px: 2,
py: 1,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
whiteSpace: 'nowrap',
}}
>
<Box
component="span"
sx={{
overflow: 'hidden',
textOverflow: 'ellipsis',
direction: 'rtl',
}}
>
{t('settingForm.addEmailOrSocialButton')}
</Box>
<ArrowDown3 size="20" color="#2979FF" />
</Button>
</Box>
<Menu
anchorEl={anchor}
open={openMenu}
onClose={handleCloseMenu}
PaperProps={{
sx: {
minWidth: 240,
maxWidth: '90vw',
},
}}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
>
<MenuItem
onClick={() => {
handleCloseMenu();
handleOpenDialog();
}}
>
<ListItemIcon>
<Message size={20} color="black" />
</ListItemIcon>
<ListItemText>{t('settingForm.email')}</ListItemText>
</MenuItem>
<MenuItem>
<ListItemIcon>
<Google size={20} color="#4285F4" />
</ListItemIcon>
<ListItemText>{t('settingForm.google')}</ListItemText>
</MenuItem>
<MenuItem>
<ListItemIcon>
<Apple size={20} color="black" />
</ListItemIcon>
<ListItemText>{t('settingForm.apple')}</ListItemText>
</MenuItem>
</Menu>
</Box>
}
>
<Box sx={{ width: '100%', borderRadius: '8px', p: { xs: 1, sm: 2 } }}>
{emailList.map((item, index) => (
<Box
key={index}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexWrap: 'wrap',
py: 1,
gap: 1,
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
minWidth: 0,
}}
>
{item.provider === 'google' && (
<Google size="20" variant="Bold" color="#4285F4" />
)}
{item.provider === 'apple' && (
<Apple size="20" variant="Bold" color="black" />
)}
{item.provider === 'email' && (
<Sms size="20" variant="Bold" color="#1976d2" />
)}
<Box sx={{ minWidth: 0 }}>
<Typography variant="h6" noWrap>
{item.email}
</Typography>
<Typography variant="caption" color="text.secondary">
{item.time}
</Typography>
</Box>
</Box>
<IconButton size="small">
<Trash size="20" color="gray" variant="Outline" />
</IconButton>
</Box>
))}
</Box>
<Dialog
open={openDialog}
onClose={handleCloseDialog}
fullWidth
maxWidth="xs"
fullScreen={fullScreen}
sx={{
'& .MuiDialog-paper': { m: { xs: 1, sm: 'auto' }, borderRadius: 2 },
}}
>
<DialogTitle
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
fontWeight: 'bold',
fontSize: '16px',
gap: 1,
}}
>
<IconButton onClick={handleCloseDialog} size="small">
<CloseSquare size="24" color="#F5F5F5" />
</IconButton>
{t('settingForm.addEmailButton')}
</DialogTitle>
<DialogContent>
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Box>
<Typography fontWeight="bold">
{t('settingForm.newEmail')}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('settingForm.dialogHeader')}
</Typography>
</Box>
<TextField
fullWidth
type="email"
value={emailInput}
onChange={handleEmailChange}
error={emailError}
helperText={emailError ? t('settingForm.emailError') : ''}
label={t('settingForm.email')}
placeholder="abc@email.com"
sx={{ '& .MuiOutlinedInput-root': { borderRadius: '8px' } }}
/>
<Button
variant="contained"
fullWidth
sx={{ textTransform: 'none', borderRadius: '8px' }}
disabled={emailError || emailInput === ''}
>
{t('settingForm.verificationCodeButton')}
</Button>
<Box sx={{ display: 'flex', alignItems: 'center', my: 2 }}>
<Box sx={{ flex: 1, height: 1, bgcolor: '#ccc' }} />
<Typography sx={{ mx: 1, fontSize: '12px', color: 'gray' }}>
{t('settingForm.or')}
</Typography>
<Box sx={{ flex: 1, height: 1, bgcolor: '#ccc' }} />
</Box>
<Box
sx={{
display: 'flex',
gap: 1,
flexDirection: { xs: 'column', sm: 'row' },
}}
>
<Button
fullWidth
sx={{
textTransform: 'none',
border: 2,
borderColor: '#1976d2',
color: '#1976d2',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Google
size="20"
color="#4285F4"
style={{ marginInlineStart: 8 }}
/>
{t('settingForm.google')}
</Button>
<Button
fullWidth
sx={{
textTransform: 'none',
border: 2,
borderColor: '#1976d2',
color: '#1976d2',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Apple
size="20"
color="black"
style={{ marginInlineStart: 8 }}
/>
{t('settingForm.apple')}
</Button>
</Box>
</Box>
</DialogContent>
</Dialog>
</CardContainer>
);
}

View File

@@ -0,0 +1,14 @@
import { Box } from '@mui/material';
import { PersonalInformation } from './PersonalInformation';
import { PhoneNumber } from './PhoneNumber';
import { SocialMedia } from './SocialMedia';
export function UserForm() {
return (
<Box sx={{ width: '100%', overflowX: 'clip' }}>
<PersonalInformation />
<PhoneNumber />
<SocialMedia />
</Box>
);
}

View File

@@ -0,0 +1,24 @@
import { Box, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
export function DisplayField({
label,
value,
}: {
label: string;
value: string;
}) {
const { t } = useTranslation('profileSetting');
const displayValue = value?.trim() || t('settingForm.notDetermined');
return (
<Box sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }}>
<Typography variant="caption" color="text.secondary">
{label}
</Typography>
<Typography variant="body1" color="text.primary">
{displayValue}
</Typography>
</Box>
);
}

View File

@@ -0,0 +1,85 @@
import { Box, Typography, Avatar } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CountryFlag } from '@/components/CountryFlag';
import { DisplayField } from './DisplayField';
export function InfoRowDisplay({
data,
uploadedImageUrl,
initials,
}: {
data: any;
uploadedImageUrl: string | null;
initials: string;
}) {
const { t } = useTranslation('profileSetting');
const displayValue = (value: string) =>
value?.trim() || t('settingForm.notDetermined');
return (
<Box sx={{ px: 2, mb: 2 }}>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
flexWrap: 'wrap',
alignItems: { xs: 'flex-start', sm: 'center' },
gap: 2,
justifyContent: 'space-between',
width: '690px',
}}
>
<Box
sx={{ display: 'flex', alignItems: 'center', gap: 2, width: '337px' }}
>
<Avatar
src={uploadedImageUrl || undefined}
sx={{
width: 32,
height: 32,
bgcolor: 'secondary.main',
fontSize: '16px',
}}
>
{initials}
</Avatar>
<Box>
<Typography variant="caption" color="text.secondary">
{t('settingForm.name')} و {t('settingForm.familyName')}
</Typography>
<Typography variant="body1" color="text.primary">
{`${displayValue(data.firstName)} ${displayValue(data.lastName)}`}
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', width: '337px' }}>
<Typography variant="caption" color="text.secondary">
{t('settingForm.country')}
</Typography>
<CountryFlag country={data.country} />
</Box>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
flexWrap: 'wrap',
gap: 2,
mt: 2,
width: '690px',
}}
>
<DisplayField
label={t('settingForm.gender')}
value={displayValue(data.gender)}
/>
<DisplayField
label={t('settingForm.nationalCode')}
value={displayValue(data.nationalCode)}
/>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,97 @@
import {
Box,
TextField,
FormControl,
MenuItem,
Select,
Autocomplete,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CountryFlag, countryCodeMap } from '@/components/CountryFlag';
export function InfoRowEdit({ data, setData, gender, setGender }: any) {
const { t } = useTranslation('profileSetting');
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
{[
{
name: 'firstName',
label: t('settingForm.name'),
value: data.firstName,
},
{
name: 'lastName',
label: t('settingForm.familyName'),
value: data.lastName,
},
{
name: 'nationalCode',
label: t('settingForm.nationalCode'),
value: data.nationalCode,
},
].map((field, idx) => (
<Box
key={idx}
sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }}
>
<TextField
fullWidth
name={field.name}
value={field.value}
onChange={(e) =>
setData((prev: any) => ({
...prev,
[field.name]: e.target.value,
}))
}
label={field.label}
/>
</Box>
))}
<Box sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }}>
<FormControl fullWidth>
<Select
value={gender}
onChange={(e) => setGender(e.target.value)}
displayEmpty
renderValue={(selected) =>
selected ? (
selected === 'male' ? (
t('settingForm.man')
) : (
t('settingForm.woman')
)
) : (
<span style={{ color: '#aaa' }}>
{t('settingForm.genderPlaceholder')}
</span>
)
}
>
<MenuItem value="male">{t('settingForm.man')}</MenuItem>
<MenuItem value="female">{t('settingForm.woman')}</MenuItem>
</Select>
</FormControl>
</Box>
<Autocomplete
sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }}
options={Object.keys(countryCodeMap)}
value={data.country}
onChange={(_, newValue) =>
setData((prev: any) => ({ ...prev, country: newValue || '' }))
}
renderOption={(props, option) => (
<Box component="li" {...props}>
<CountryFlag country={option} />
</Box>
)}
renderInput={(params) => (
<TextField {...params} label={t('settingForm.country')} />
)}
/>
</Box>
);
}

View File

@@ -0,0 +1,62 @@
import { Box, Avatar, Typography, Button } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { Camera } from 'iconsax-react';
export function ProfileImage({
initials,
uploadedImageUrl,
onImageChange,
}: {
initials: string;
uploadedImageUrl: string | null;
onImageChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}) {
const { t } = useTranslation('profileSetting');
return (
<Box
sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}
>
<Avatar
sx={{
bgcolor: 'secondary.main',
width: 88,
height: 88,
fontSize: '20px',
}}
src={uploadedImageUrl || undefined}
>
{initials}
</Avatar>
<Box>
<Typography variant="body1">
{t('settingForm.profilePicture')}
</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={onImageChange}
/>
</Button>
</Box>
</Box>
</Box>
);
}