fix: code styles

This commit is contained in:
Koosha Lahouti
2025-08-10 16:21:25 -07:00
parent 57959f39ce
commit 8e6c09225d
28 changed files with 613 additions and 568 deletions

View File

@@ -40,6 +40,7 @@
"notDetermined": "Not determined", "notDetermined": "Not determined",
"successfulChangePhone": "Phone number changed successfully", "successfulChangePhone": "Phone number changed successfully",
"phoneNumberIsInvalid": "Phone number is invalid", "phoneNumberIsInvalid": "Phone number is invalid",
"thisFieldIsRequired": "This field is requried" "thisFieldIsRequired": "This field is required",
"changePicture": "Change picture"
} }
} }

View File

@@ -40,6 +40,7 @@
"notDetermined": "تعیین نشده", "notDetermined": "تعیین نشده",
"successfulChangePhone": "شماره تماس با موفقیت تغییر کرد", "successfulChangePhone": "شماره تماس با موفقیت تغییر کرد",
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد", "phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
"thisFieldIsRequired": "این فیلد الزامی است" "thisFieldIsRequired": "این فیلد الزامی است",
"changePicture": "تغییر تصویر"
} }
} }

View File

@@ -3,6 +3,7 @@ import './App.css';
import { LanguageManager } from './components/LanguageManager'; import { LanguageManager } from './components/LanguageManager';
import { Settings } from './features/profile/routes/SettingPage'; import { Settings } from './features/profile/routes/SettingPage';
function App() { function App() {
return ( return (
<> <>

View File

@@ -1,19 +1,21 @@
import React from 'react'; import React from 'react';
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
interface CardContainerProps {
title: string;
subtitle: string;
action?: React.ReactNode;
children?: React.ReactNode;
highlighted?: boolean;
}
export function CardContainer({ export function CardContainer({
title, title,
subtitle, subtitle,
action, action,
children, children,
highlighted, highlighted,
}: { }: CardContainerProps) {
title: string;
subtitle: string;
action?: React.ReactNode;
children?: React.ReactNode;
highlighted?: boolean;
}) {
return ( return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}> <Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box <Box
@@ -21,7 +23,7 @@ export function CardContainer({
marginInline: 'auto', marginInline: 'auto',
width: '100%', width: '100%',
maxWidth: 'min(100%, 818px)', maxWidth: 'min(100%, 818px)',
paddingInline: { xs: 2, sm: 3, md: 4 }, // paddingInline: { xs: 2, sm: 3, md: 4 },
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 2, gap: 2,

View File

@@ -76,7 +76,7 @@ export function CountryCodeSelector({
country.label.toLowerCase().includes(searchTerm.toLowerCase()) || country.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
country.phone.includes(searchTerm), country.phone.includes(searchTerm),
), ),
[searchTerm], [searchTerm, t],
); );
return ( return (

View File

@@ -7,10 +7,10 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { DeviceMessage, Logout } from 'iconsax-react'; import { DeviceMessage, Logout } from 'iconsax-react';
import Logo from '@/components/Logo';
import { CardContainer } from '@/components/CardContainer'; import { CardContainer } from '@/components/CardContainer';
import { PageWrapper } from '../PageWrapper'; import { PageWrapper } from '../PageWrapper';
import React from 'react'; import React from 'react';
import { Icon } from '@rkheftan/harmony-ui';
export function ActiveDevices() { export function ActiveDevices() {
const { t } = useTranslation('activeDevices'); const { t } = useTranslation('activeDevices');
@@ -50,24 +50,17 @@ export function ActiveDevices() {
return ( return (
<PageWrapper> <PageWrapper>
<Box sx={{ display: 'flex', alignItems: 'center', py: 2, height: 84 }}>
<Logo />
</Box>
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
<CardContainer <CardContainer
title={t('active.activeDevices')} title={t('active.activeDevices')}
subtitle={t('active.activeDevicesCaption')} subtitle={t('active.activeDevicesCaption')}
action={ action={
<Button <Button
size="medium"
variant="outlined" variant="outlined"
sx={{ sx={{
borderRadius: '10px', borderRadius: 1,
borderColor: 'error.main', borderColor: 'error.main',
color: 'error.main', color: 'error.main',
textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
}} }}
> >
{t('active.deletDevicesButton')} {t('active.deletDevicesButton')}
@@ -116,7 +109,11 @@ export function ActiveDevices() {
order: { xs: 2, sm: 2 }, order: { xs: 2, sm: 2 },
}} }}
> >
<DeviceMessage size={24} color="#82B1FF" /> <Icon
Component={DeviceMessage}
size="medium"
color="primary.main"
/>
<Typography variant="body2" noWrap> <Typography variant="body2" noWrap>
{device.deviceModel} {device.deviceModel}
</Typography> </Typography>
@@ -138,22 +135,22 @@ export function ActiveDevices() {
sx={{ sx={{
flexBasis: { xs: '100%', sm: 'auto' }, flexBasis: { xs: '100%', sm: 'auto' },
mb: { xs: 1, sm: 0 }, mb: { xs: 1, sm: 0 },
textAlign: { xs: 'left', sm: 'center' },
minWidth: { sm: '138px' }, minWidth: { sm: '138px' },
order: { xs: 4, sm: 4 }, order: { xs: 4, sm: 4 },
alignItems: 'center',
justifyContent: 'center',
}} }}
> >
{device.current && ( {device.current && (
<Button <Button
variant="outlined" variant="outlined"
size="medium"
sx={{ sx={{
borderRadius: '15px', borderRadius: '100px',
border: '2px solid', border: '1px solid',
borderColor: 'success.main', borderColor: 'success.main',
height: '30px',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
color: 'success.main', color: 'success.main',
textTransform: 'none',
}} }}
> >
{t('active.currentDevice')} {t('active.currentDevice')}
@@ -166,26 +163,29 @@ export function ActiveDevices() {
flexBasis: { xs: '100%', sm: 'auto' }, flexBasis: { xs: '100%', sm: 'auto' },
mb: { xs: 1, sm: 0 }, mb: { xs: 1, sm: 0 },
textAlign: { xs: 'left', sm: 'center' }, textAlign: { xs: 'left', sm: 'center' },
minWidth: { sm: '150px' }, minWidth: { sm: '138px' },
order: { xs: 5, sm: 5 }, order: { xs: 5, sm: 5 },
}} }}
> >
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
startIcon={<Logout size={18} color="#E53935" />} startIcon={
<Icon
Component={Logout}
size="small"
color="error.main"
/>
}
disabled={device.current} disabled={device.current}
sx={{ sx={{
color: 'error.main', color: 'error.main',
minWidth: 0, borderRadius: 1,
borderRadius: '15px',
borderColor: 'error.main', borderColor: 'error.main',
p: '4px 8px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
textTransform: 'none',
'& .MuiButton-startIcon': { '& .MuiButton-startIcon': {
marginRight: '4px', marginRight: '4px',
marginLeft: 0, marginLeft: 0,

View File

@@ -8,7 +8,7 @@ import { RecentLogins } from '../security/RecentLogins';
import { ActiveDevices } from '../activeDevices/ActiveDevices'; import { ActiveDevices } from '../activeDevices/ActiveDevices';
import { Setting } from '../setting/Setting'; import { Setting } from '../setting/Setting';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
// tooye route ye component taarif mikonim be esme profile bad mibarim tooye route
export const router = createBrowserRouter([ export const router = createBrowserRouter([
{ {
path: '/', path: '/',

View File

@@ -8,9 +8,11 @@ import {
Box, Box,
Button, Button,
Link, Link,
CircularProgress,
} from '@mui/material'; } from '@mui/material';
import { CloseCircle, Refresh } from 'iconsax-react'; import { CloseCircle } from 'iconsax-react';
import { PasswordValidationItem } from './PasswordValidation'; import { PasswordValidationItem } from './PasswordValidation';
import { Icon } from '@rkheftan/harmony-ui';
interface PasswordDialogProps { interface PasswordDialogProps {
open: boolean; open: boolean;
@@ -68,7 +70,7 @@ export function PasswordDialog({
<DialogTitle sx={{ p: 2 }}> <DialogTitle sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton onClick={handleClose}> <IconButton onClick={handleClose}>
<CloseCircle size={24} color="#82B1FF" /> <Icon Component={CloseCircle} size="large" color="primary.main" />
</IconButton> </IconButton>
<Box component="span" fontWeight="bold" fontSize={18}> <Box component="span" fontWeight="bold" fontSize={18}>
{t('securityForm.addPassword')} {t('securityForm.addPassword')}
@@ -150,28 +152,12 @@ export function PasswordDialog({
<DialogActions sx={{ px: 3, pb: 2, justifyContent: 'center' }}> <DialogActions sx={{ px: 3, pb: 2, justifyContent: 'center' }}>
<Button <Button
fullWidth fullWidth
sx={{ maxWidth: '364px', height: 48, textTransform: 'none' }} sx={{ height: 48, textTransform: 'none' }}
variant="contained" variant="contained"
onClick={handleShowAlert} onClick={handleShowAlert}
disabled={!password || password !== confirmPassword || loading} disabled={!password || password !== confirmPassword || loading}
> >
{loading ? ( {loading ? <CircularProgress size={20} /> : t('securityForm.confirm')}
<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="#fff" />
</Box>
) : (
t('securityForm.confirm')
)}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -9,7 +9,6 @@ import {
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer'; import { CardContainer } from '@/components/CardContainer';
import { PageWrapper } from '../PageWrapper'; import { PageWrapper } from '../PageWrapper';
import Logo from '@/components/Logo';
import { PasswordDialog } from './PasswordDialog'; import { PasswordDialog } from './PasswordDialog';
import { Toast } from '@/components/Toast'; import { Toast } from '@/components/Toast';
@@ -66,10 +65,6 @@ export function PasswordSecurity() {
return ( return (
<PageWrapper> <PageWrapper>
<Box sx={{ display: 'flex', alignItems: 'center', py: 2, height: 84 }}>
<Logo />
</Box>
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
<CardContainer <CardContainer
title={t('securityForm.password')} title={t('securityForm.password')}
subtitle={t('securityForm.determinePassword')} subtitle={t('securityForm.determinePassword')}
@@ -78,11 +73,8 @@ export function PasswordSecurity() {
variant="outlined" variant="outlined"
onClick={handleOpen} onClick={handleOpen}
sx={{ sx={{
mt: { xs: 2, sm: 0 },
backgroundColor: 'primary.main', backgroundColor: 'primary.main',
color: 'background.paper', color: 'background.paper',
width: { xs: '100%', sm: '142px' },
textTransform: 'none',
}} }}
> >
{changePassword {changePassword
@@ -121,7 +113,6 @@ export function PasswordSecurity() {
</Typography> </Typography>
)} )}
{/* New PasswordDialog component usage */}
<PasswordDialog <PasswordDialog
open={open} open={open}
fullScreen={fullScreen} fullScreen={fullScreen}

View File

@@ -1,5 +1,6 @@
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { TickCircle } from 'iconsax-react'; import { TickCircle } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
interface ValidationItemProps { interface ValidationItemProps {
isValid: boolean; isValid: boolean;
@@ -20,10 +21,11 @@ export function PasswordValidationItem({
flexWrap: 'wrap', flexWrap: 'wrap',
}} }}
> >
<TickCircle <Icon
size="16" Component={TickCircle}
color={isValid ? '#14AE5C' : '#2979FF'} color={isValid ? 'success.main' : 'primary.main'}
variant={isValid ? 'Bold' : 'Outline'} variant={isValid ? 'Bold' : 'Outline'}
size="small"
/> />
<Typography <Typography
variant="body2" variant="body2"

View File

@@ -45,7 +45,7 @@ export function RecentLogins() {
}, },
}} }}
> >
<Box sx={{ width: '100%', maxWidth: '754px', px: 4 }}> <Box sx={{ px: 4 }}>
{data.map((d) => ( {data.map((d) => (
<React.Fragment key={d.id}> <React.Fragment key={d.id}>
<Box <Box

View File

@@ -10,7 +10,6 @@ import {
import { CardContainer } from '@/components/CardContainer'; import { CardContainer } from '@/components/CardContainer';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ThemeToggleButton } from '@/components/ThemToggle'; import { ThemeToggleButton } from '@/components/ThemToggle';
import Logo from '@/components/Logo';
import { PageWrapper } from '../PageWrapper'; import { PageWrapper } from '../PageWrapper';
export function Setting() { export function Setting() {
@@ -37,7 +36,7 @@ export function Setting() {
]; ];
const handleDraftLanguageChange = ( const handleDraftLanguageChange = (
_: any, _: React.SyntheticEvent,
v: { code: string; label: string } | null, v: { code: string; label: string } | null,
) => v && setDraftLanguage(v.code); ) => v && setDraftLanguage(v.code);
@@ -63,16 +62,10 @@ export function Setting() {
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
minHeight: '100vh',
}} }}
> >
<Box sx={{ display: 'flex', alignItems: 'center', py: 2, height: 84 }}>
<Logo />
</Box>
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
<Box <Box
sx={{ sx={{
width: '100%',
px: { xs: 0, sm: 3 }, px: { xs: 0, sm: 3 },
mx: 0, mx: 0,
}} }}
@@ -91,7 +84,7 @@ export function Setting() {
sx={{ sx={{
color: 'primary.main', color: 'primary.main',
textTransform: 'none', textTransform: 'none',
width: { xs: '100%', sm: 'auto' }, // width: { xs: '100%', sm: 'auto' },
fontSize: { xs: '0.85rem', sm: '1rem' }, fontSize: { xs: '0.85rem', sm: '1rem' },
}} }}
> >
@@ -103,16 +96,11 @@ export function Setting() {
size="large" size="large"
variant="outlined" variant="outlined"
sx={{ sx={{
textTransform: 'none',
border: '1px solid', border: '1px solid',
borderColor: 'primary.main', borderColor: 'primary.main',
borderRadius: '4px', borderRadius: 1,
bgcolor: isEditing ? 'primary.main' : 'background.paper', bgcolor: isEditing ? 'primary.main' : 'background.default',
color: isEditing ? 'primary.contrastText' : 'primary.main', 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 {isEditing
@@ -130,7 +118,6 @@ export function Setting() {
flexDirection: 'column', flexDirection: 'column',
gap: 2, gap: 2,
bgcolor: 'background.paper', bgcolor: 'background.paper',
width: '100%',
}} }}
> >
<Box <Box
@@ -139,7 +126,6 @@ export function Setting() {
flexDirection: { xs: 'column', sm: 'row' }, flexDirection: { xs: 'column', sm: 'row' },
gap: 2, gap: 2,
mt: 2, mt: 2,
width: '100%',
}} }}
> >
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>

View File

@@ -1,74 +1,53 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { Box, Button, useTheme, useMediaQuery } from '@mui/material'; import { Box, Button } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer'; import { CardContainer } from '@/components/CardContainer';
import { ProfileImage } from './personlInformation/ProfileImage'; import { ProfileImage } from './personalInformation/ProfileImage';
import { InfoRowDisplay } from './personlInformation/InfoRowDisplay'; import { InfoRowDisplay } from './personalInformation/InfoRowDisplay';
import { InfoRowEdit } from './personlInformation/InfoRowEdit'; import { InfoRowEdit } from './personalInformation/InfoRowEdit';
import Logo from '@/components/Logo';
import { PageWrapper } from '../PageWrapper'; import { PageWrapper } from '../PageWrapper';
import { Gender, type InfoRowData } from '../../types';
export function PersonalInformation() { export function PersonalInformation() {
const { t } = useTranslation('profileSetting'); const { t } = useTranslation('profileSetting');
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [gender, setGender] = useState('');
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null); const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
const theme = useTheme();
const isMdUp = useMediaQuery(theme.breakpoints.up('lg'));
const initialData = { const initialData: InfoRowData = {
firstName: 'محمد حسین', firstName: 'محمد حسین',
lastName: 'برزه‌گر', lastName: 'برزه‌گر',
country: 'قطر', country: 'قطر',
gender: 'مرد',
nationalCode: '', nationalCode: '',
gender: Gender.None,
}; };
const [data, setData] = useState(initialData);
const [data, setData] = useState<InfoRowData>(initialData);
const [gender, setGender] = useState<Gender>(Gender.None);
useEffect(() => {
if (Object.values(Gender).includes(data.gender)) {
setGender(data.gender);
}
}, [data.gender]);
const initials = `${data.firstName?.trim()[0] || ''}${data.lastName?.trim()[0] || ''}`; const initials = `${data.firstName?.trim()[0] || ''}${data.lastName?.trim()[0] || ''}`;
const toggleEdit = () => { const toggleEdit = () => {
setIsEditing((prev) => !prev);
if (isEditing) { if (isEditing) {
setData((prev) => ({ setData((prev) => ({
...prev, ...prev,
gender: gender: gender,
gender === 'male'
? t('settingForm.man')
: gender === 'female'
? t('settingForm.woman')
: '',
})); }));
} else { } else {
setGender( setGender(
data.gender === t('settingForm.man') Object.values(Gender).includes(data.gender) ? data.gender : Gender.None,
? 'male'
: data.gender === t('settingForm.woman')
? 'female'
: '',
); );
} }
setIsEditing(!isEditing);
}; };
return ( return (
<PageWrapper> <PageWrapper>
{isMdUp && (
<>
<Box
sx={{
display: 'flex',
alignItems: 'center',
py: 2,
height: 84,
}}
>
<Logo />
</Box>
<Box
sx={{ width: '100%', height: 1, bgcolor: 'background.default' }}
/>
</>
)}
<CardContainer <CardContainer
title={t('settingForm.titlePersonalInfo')} title={t('settingForm.titlePersonalInfo')}
subtitle={t('settingForm.descriptionPersonalInfo')} subtitle={t('settingForm.descriptionPersonalInfo')}
@@ -84,7 +63,7 @@ export function PersonalInformation() {
color: 'primary.main', color: 'primary.main',
textTransform: 'none', textTransform: 'none',
width: { xs: '100%', sm: 'auto' }, width: { xs: '100%', sm: 'auto' },
fontSize: { xs: '0.85rem', sm: '1rem' }, // fontSize: { xs: '0.8 5rem', sm: '1rem' },
}} }}
> >
{t('settingForm.rejectButton')} {t('settingForm.rejectButton')}
@@ -95,16 +74,9 @@ export function PersonalInformation() {
size="large" size="large"
variant="outlined" variant="outlined"
sx={{ sx={{
textTransform: 'none', borderRadius: 1,
border: '1px solid', bgcolor: isEditing ? 'primary.main' : 'background.default',
borderColor: 'primary.main',
borderRadius: '4px',
bgcolor: isEditing ? 'primary.main' : 'background.paper',
color: isEditing ? 'primary.contrastText' : 'primary.main', 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 {isEditing
@@ -116,7 +88,7 @@ export function PersonalInformation() {
> >
<Box <Box
sx={{ sx={{
px: { xs: 2, sm: 3, md: 4 }, mx: { xs: 2, sm: 3, md: 4 },
py: 2, py: 2,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@@ -128,15 +100,13 @@ export function PersonalInformation() {
<ProfileImage <ProfileImage
initials={initials} initials={initials}
uploadedImageUrl={uploadedImageUrl} uploadedImageUrl={uploadedImageUrl}
onImageChange={(e) => { onImageChange={(file) => {
const file = e.target.files?.[0]; const reader = new FileReader();
if (file) { reader.onload = () =>
const reader = new FileReader(); setUploadedImageUrl(reader.result as string);
reader.onload = () => reader.readAsDataURL(file);
setUploadedImageUrl(reader.result as string);
reader.readAsDataURL(file);
}
}} }}
onRemoveImage={() => setUploadedImageUrl(null)}
/> />
)} )}

View File

@@ -29,7 +29,6 @@ export function SocialMedia() {
const emailList = [ const emailList = [
{ email: 'emailtemp@email.com', provider: 'email', time: '1 ماه پیش' }, { email: 'emailtemp@email.com', provider: 'email', time: '1 ماه پیش' },
{ email: 'emailtemp@gmail.com', provider: 'google', time: '1 ماه پیش' }, { email: 'emailtemp@gmail.com', provider: 'google', time: '1 ماه پیش' },
{ email: 'emailtemp@icloud.com', provider: 'apple', time: '1 ماه پیش' },
] as const; ] as const;
return ( return (

View File

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

View File

@@ -0,0 +1,106 @@
import { Box, Typography, Avatar } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CountryFlag } from '@/components/CountryFlag';
import { DisplayField } from './DisplayField';
import { Gender } from '@/features/profile/types';
interface InfoRowData {
firstName: string;
lastName: string;
country: string;
gender: Gender | '';
nationalCode: string;
}
interface InfoRowDisplayProps {
data: InfoRowData;
uploadedImageUrl: string | null;
initials: string;
}
export function InfoRowDisplay({
data,
uploadedImageUrl,
initials,
}: InfoRowDisplayProps) {
const { t } = useTranslation('profileSetting');
const displayValue = (value: string) =>
value?.trim() || t('settingForm.notDetermined');
const getGenderLabel = (gender: Gender | '') => {
switch (gender) {
case Gender.Male:
return t('settingForm.man');
case Gender.Female:
return t('settingForm.woman');
default:
return t('settingForm.notDetermined');
}
};
return (
<Box sx={{ mb: 2 }}>
<Box sx={{ width: '100%' }}>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 2,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flex: 1,
gap: 1,
}}
>
<Typography variant="caption" color="text.secondary">
{t('settingForm.name')} و {t('settingForm.familyName')}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Avatar
src={uploadedImageUrl || undefined}
sx={{
width: 32,
height: 32,
bgcolor: 'secondary.main',
fontSize: '16px',
}}
>
{initials}
</Avatar>
<Typography variant="body1" color="text.primary">
{`${displayValue(data.firstName)} ${displayValue(data.lastName)}`}
</Typography>
</Box>
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="caption" color="text.secondary">
{t('settingForm.country')}
</Typography>
<CountryFlag country={data.country} />
</Box>
</Box>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
gap: 2,
mt: 2,
}}
>
<DisplayField
label={t('settingForm.gender')}
value={getGenderLabel(data.gender)}
/>
<DisplayField
label={t('settingForm.nationalCode')}
value={displayValue(data.nationalCode)}
/>
</Box>
</Box>
);
}

View File

@@ -8,29 +8,45 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CountryFlag, countryCodeMap } from '@/components/CountryFlag'; import { CountryFlag, countryCodeMap } from '@/components/CountryFlag';
import { Gender } from '@/features/profile/types';
import { type InfoRowData } from '@/features/profile/types';
export function InfoRowEdit({ data, setData, gender, setGender }: any) { interface InfoRowEditProps {
data: InfoRowData;
setData: React.Dispatch<React.SetStateAction<InfoRowData>>;
gender: Gender;
setGender: React.Dispatch<React.SetStateAction<Gender>>;
}
export function InfoRowEdit({
data,
setData,
gender,
setGender,
}: InfoRowEditProps) {
const { t } = useTranslation('profileSetting'); const { t } = useTranslation('profileSetting');
const labels = [
{
name: 'firstName' as keyof InfoRowData,
label: t('settingForm.name'),
value: data.firstName,
},
{
name: 'lastName' as keyof InfoRowData,
label: t('settingForm.familyName'),
value: data.lastName,
},
{
name: 'nationalCode' as keyof InfoRowData,
label: t('settingForm.nationalCode'),
value: data.nationalCode,
},
];
return ( return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}> <Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
{[ {labels.map((field, idx) => (
{
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 <Box
key={idx} key={idx}
sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }} sx={{ width: { xs: '100%', sm: '48%', md: 'calc(50% - 8px)' } }}
@@ -40,7 +56,7 @@ export function InfoRowEdit({ data, setData, gender, setGender }: any) {
name={field.name} name={field.name}
value={field.value} value={field.value}
onChange={(e) => onChange={(e) =>
setData((prev: any) => ({ setData((prev) => ({
...prev, ...prev,
[field.name]: e.target.value, [field.name]: e.target.value,
})) }))
@@ -54,24 +70,22 @@ export function InfoRowEdit({ data, setData, gender, setGender }: any) {
<FormControl fullWidth> <FormControl fullWidth>
<Select <Select
value={gender} value={gender}
onChange={(e) => setGender(e.target.value)} onChange={(e) => setGender(e.target.value as Gender)}
displayEmpty displayEmpty
renderValue={(selected) => renderValue={(selected) =>
selected ? ( selected ? (
selected === 'male' ? ( selected === Gender.Male ? (
t('settingForm.man') t('settingForm.man')
) : ( ) : (
t('settingForm.woman') t('settingForm.woman')
) )
) : ( ) : (
<span style={{ color: '#aaa' }}> <span>{t('settingForm.genderPlaceholder')}</span>
{t('settingForm.genderPlaceholder')}
</span>
) )
} }
> >
<MenuItem value="male">{t('settingForm.man')}</MenuItem> <MenuItem value={Gender.Male}>{t('settingForm.man')}</MenuItem>
<MenuItem value="female">{t('settingForm.woman')}</MenuItem> <MenuItem value={Gender.Female}>{t('settingForm.woman')}</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
</Box> </Box>
@@ -81,7 +95,7 @@ export function InfoRowEdit({ data, setData, gender, setGender }: any) {
options={Object.keys(countryCodeMap)} options={Object.keys(countryCodeMap)}
value={data.country} value={data.country}
onChange={(_, newValue) => onChange={(_, newValue) =>
setData((prev: any) => ({ ...prev, country: newValue || '' })) setData((prev) => ({ ...prev, country: newValue || '' }))
} }
renderOption={(props, option) => ( renderOption={(props, option) => (
<Box component="li" {...props}> <Box component="li" {...props}>

View File

@@ -0,0 +1,112 @@
import React, { useState } from 'react';
import { Box, Avatar, Typography, Button, IconButton } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { Camera, Trash } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
const MAX_FILE_SIZE_MB = 10;
interface ProfileImageProps {
initials: string;
uploadedImageUrl: string | null;
onImageChange: (file: File) => void;
onRemoveImage?: () => void;
}
export function ProfileImage({
initials,
uploadedImageUrl,
onImageChange,
onRemoveImage,
}: ProfileImageProps) {
const { t } = useTranslation('profileSetting');
const [error, setError] = useState<string | null>(null);
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setError(null);
const file = e.target.files?.[0];
if (!file) return;
const fileSizeMB = file.size / (1024 * 1024);
if (fileSizeMB > MAX_FILE_SIZE_MB) {
setError(t('settingForm.fileSizeError', { size: MAX_FILE_SIZE_MB }));
return;
}
onImageChange(file);
};
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>
{error && (
<Typography variant="body2" color="error" mt={1}>
{error}
</Typography>
)}
<Box mt={1} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Button
variant={
uploadedImageUrl && onRemoveImage ? 'outlined' : 'contained'
}
component="label"
size="small"
sx={{
borderRadius: 2,
textTransform: 'none',
}}
startIcon={
<Icon
Component={Camera}
size="small"
color={
uploadedImageUrl && onRemoveImage ? 'primary.main' : 'white'
}
/>
}
>
{uploadedImageUrl && onRemoveImage
? t('settingForm.changePicture')
: t('settingForm.uploadPicture')}
<input
hidden
accept="image/png, image/jpeg, image/gif"
type="file"
onChange={handleImageChange}
/>
</Button>
{uploadedImageUrl && onRemoveImage && (
<IconButton
size="small"
color="error"
onClick={onRemoveImage}
aria-label={t('settingForm.removePicture')}
>
<Icon Component={Trash} color="error.main" />
</IconButton>
)}
</Box>
</Box>
</Box>
);
}

View File

@@ -1,90 +0,0 @@
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={{
width: '337px',
display: 'flex',
flexDirection: 'column',
gap: 1,
}}
>
<Typography variant="caption" color="text.secondary">
{t('settingForm.name')} و {t('settingForm.familyName')}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Avatar
src={uploadedImageUrl || undefined}
sx={{
width: 32,
height: 32,
bgcolor: 'secondary.main',
fontSize: '16px',
}}
>
{initials}
</Avatar>
<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

@@ -1,62 +0,0 @@
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>
);
}

View File

@@ -1,14 +1,16 @@
import { Box, Button } from '@mui/material'; import { Box, Button } from '@mui/material';
interface PhoneActionButtonsProps {
isEditing: boolean;
toggleEdit: () => void;
t: (key: string) => string;
}
export default function PhoneActionButtons({ export default function PhoneActionButtons({
isEditing, isEditing,
toggleEdit, toggleEdit,
t, t,
}: { }: PhoneActionButtonsProps) {
isEditing: boolean;
toggleEdit: () => void;
t: (key: string) => string;
}) {
return ( return (
<Box sx={{ display: 'flex', gap: 1 }}> <Box sx={{ display: 'flex', gap: 1 }}>
{isEditing && ( {isEditing && (
@@ -19,7 +21,7 @@ export default function PhoneActionButtons({
sx={{ sx={{
color: 'primary.main', color: 'primary.main',
textTransform: 'none', textTransform: 'none',
width: '43px', width: { xs: '100%', sm: 'auto' },
}} }}
> >
{t('settingForm.rejectButton')} {t('settingForm.rejectButton')}
@@ -30,13 +32,9 @@ export default function PhoneActionButtons({
size="large" size="large"
variant="outlined" variant="outlined"
sx={{ sx={{
textTransform: 'none', borderRadius: 1,
border: '1px solid', bgcolor: isEditing ? 'primary.main' : 'background.default',
borderColor: 'primary.main',
borderRadius: '4px',
bgcolor: isEditing ? 'primary.main' : 'background.paper',
color: isEditing ? 'primary.contrastText' : 'primary.main', color: isEditing ? 'primary.contrastText' : 'primary.main',
width: { xs: '100%', sm: isEditing ? '85px' : '161px' },
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}} }}
> >

View File

@@ -1,31 +1,55 @@
import { Box, Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { Icon } from '@rkheftan/harmony-ui';
import { Mobile } from 'iconsax-react';
export default function PhoneDisplay({ interface PhoneDisplayProps {
phones,
}: {
phones: { phone: string; time: string }[]; phones: { phone: string; time: string }[];
}) { }
export default function PhoneDisplay({ phones }: PhoneDisplayProps) {
return ( return (
<Box sx={{ px: { xs: 2, sm: 4 } }}> <>
{phones.map((item, index) => ( {phones.map((item, index) => (
<Box <Box
key={index} key={index}
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'row',
justifyContent: 'center', alignItems: 'center',
py: 2, py: 2,
width: '100%', mx: 3,
gap: 1,
}} }}
> >
<Typography variant="h6" color="text.primary"> <Box
{item.phone} sx={{
</Typography> width: 40,
<Typography variant="caption" color="text.secondary"> height: 40,
{item.time} backgroundColor: 'primary.light',
</Typography> justifyContent: 'center',
alignItems: 'center',
display: 'flex',
borderRadius: 0.5,
}}
>
<Icon
Component={Mobile}
size="medium"
variant="Bold"
color="primary.main"
/>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography variant="h6" color="text.primary">
{item.phone}
</Typography>
<Typography variant="caption" color="text.secondary">
{item.time}
</Typography>
</Box>
</Box> </Box>
))} ))}
</Box> </>
); );
} }

View File

@@ -1,8 +1,41 @@
import { Box, Typography, TextField, IconButton, Button } from '@mui/material'; import {
import { Edit, Refresh, TickCircle } from 'iconsax-react'; Box,
Typography,
TextField,
Button,
IconButton,
InputAdornment,
CircularProgress,
} from '@mui/material';
import { Edit2, TickCircle } from 'iconsax-react';
import { CountDownTimer } from '@/components/CountDownTimer'; import { CountDownTimer } from '@/components/CountDownTimer';
import { Toast } from '@/components/Toast'; import { Toast } from '@/components/Toast';
import { CountryCodeSelector } from '../../CountryCodeSelector'; import { CountryCodeSelector } from '../../CountryCodeSelector';
import { Icon } from '@rkheftan/harmony-ui';
interface PhoneEditFormProps {
phoneNumber: string;
setPhoneNumber: (v: string) => void;
countryCode: string;
setCountryCode: (v: string) => void;
verificationCode: string;
setVerificationCode: (v: string) => void;
isVerified: boolean;
isVerifying: boolean;
buttonState: 'default' | 'counting';
setButtonState: (v: 'default' | 'counting') => void;
handleSendCode: () => void;
handleVerifyClick: () => void;
error?: string;
inputError: boolean;
handleBlur: () => void;
textFieldRef: React.RefObject<HTMLDivElement>;
inputRef: React.RefObject<HTMLInputElement>;
phones: { phone: string; time: string; withCode: string }[];
showToast: boolean;
setShowToast: (v: boolean) => void;
t: (key: string) => string;
}
export default function PhoneEditForm({ export default function PhoneEditForm({
phoneNumber, phoneNumber,
@@ -26,41 +59,34 @@ export default function PhoneEditForm({
showToast, showToast,
setShowToast, setShowToast,
t, t,
}: { }: PhoneEditFormProps) {
phoneNumber: string; const isValidPhoneNumber = (phone: string) => {
setPhoneNumber: (v: string) => void; const digitsOnly = phone.replace(/\D/g, '');
countryCode: string; return digitsOnly.length >= 8 && digitsOnly.length <= 15;
setCountryCode: (v: string) => void; };
verificationCode: string;
setVerificationCode: (v: string) => void;
isVerified: boolean;
isVerifying: boolean;
buttonState: 'default' | 'counting';
setButtonState: (v: 'default' | 'counting') => void;
handleSendCode: () => void;
handleVerifyClick: () => void;
error?: string;
inputError: boolean;
handleBlur: () => void;
textFieldRef: React.RefObject<HTMLDivElement>;
inputRef: React.RefObject<HTMLInputElement>;
phones: { phone: string; time: string; withCode: string }[];
showToast: boolean;
setShowToast: (v: boolean) => void;
t: (key: string) => string;
}) {
return ( return (
<Box sx={{ px: { xs: 2, sm: 4 }, bgcolor: 'background.paper' }}> <>
<Box sx={{ mb: 2 }}> <Box sx={{ width: '100%' }}>
<Typography variant="h6">{t('settingForm.editPhoneNumber')}</Typography> <Box sx={{ mb: 2, mx: 3 }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="h6">
{t('settingForm.phoneNumberText')}({phones.map((p) => p.withCode)}) {t('settingForm.editPhoneNumber')}
{t('settingForm.verb')} </Typography>
</Typography> <Typography variant="body2" color="text.secondary">
{t('settingForm.phoneNumberText')}({phones.map((p) => p.withCode)})
{t('settingForm.verb')}
</Typography>
</Box>
</Box> </Box>
<Box <Box
sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center' }} sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2,
alignItems: 'center',
mx: 3,
}}
> >
<TextField <TextField
name="phoneNumber" name="phoneNumber"
@@ -73,31 +99,29 @@ export default function PhoneEditForm({
error={inputError} error={inputError}
helperText={inputError ? error : ''} helperText={inputError ? error : ''}
onChange={(e) => setPhoneNumber(e.target.value)} onChange={(e) => setPhoneNumber(e.target.value)}
sx={{ flex: '1 1 240px', minWidth: 0 }} sx={{ flex: '1 1 220px', minWidth: 0 }}
placeholder="09123456789" placeholder="09123456789"
inputProps={{ dir: 'rtl', style: { textAlign: 'right' } }}
InputProps={{ InputProps={{
endAdornment: endAdornment:
buttonState === 'counting' ? ( buttonState === 'counting' ? (
<IconButton <InputAdornment position="end">
size="small" <IconButton
onClick={() => { size="small"
setButtonState('default'); onClick={() => {
setPhoneNumber(''); setButtonState('default');
}} setPhoneNumber('');
sx={{ mr: 1 }} setVerificationCode('');
> }}
<Edit size="24" color="#64B5F6" /> edge="end"
</IconButton> >
) : null, <Icon
}} Component={Edit2}
slotProps={{ color="primary.main"
htmlInput: { variant="Bold"
dir: 'auto', />
sx: { lineHeight: 1.5, paddingX: 0 }, </IconButton>
}, </InputAdornment>
input: { ) : (
endAdornment: (
<CountryCodeSelector <CountryCodeSelector
value={countryCode} value={countryCode}
onChange={setCountryCode} onChange={setCountryCode}
@@ -106,27 +130,37 @@ export default function PhoneEditForm({
onCloseFocusRef={inputRef} onCloseFocusRef={inputRef}
/> />
), ),
},
}} }}
/> />
{isVerified ? ( {isVerified ? (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box
<TickCircle size="24" style={{ color: '#43A047' }} variant="Bold" /> sx={{
<Typography sx={{ color: 'success.main' }}> display: 'flex',
{t('settingForm.successButton')} alignItems: 'center',
</Typography> gap: 1,
color: 'success.main',
}}
>
<Icon Component={TickCircle} />
<Typography>{t('settingForm.successButton')}</Typography>
</Box> </Box>
) : ( ) : (
<Button <Button
variant="text" variant="text"
onClick={handleSendCode} onClick={() => {
disabled={buttonState === 'counting' || phoneNumber.length === 0} if (isValidPhoneNumber(phoneNumber)) {
handleSendCode();
}
}}
disabled={
buttonState === 'counting' ||
phoneNumber.length === 0 ||
!isValidPhoneNumber(phoneNumber)
}
sx={{ sx={{
textTransform: 'none', minWidth: { xs: '100%', sm: 220 },
minWidth: { xs: '100%', sm: 170 },
color: 'primary.main', color: 'primary.main',
height: 56,
}} }}
> >
{buttonState === 'counting' ? ( {buttonState === 'counting' ? (
@@ -149,6 +183,7 @@ export default function PhoneEditForm({
gap: 2, gap: 2,
mt: 2, mt: 2,
alignItems: 'center', alignItems: 'center',
mx: 3,
}} }}
> >
<TextField <TextField
@@ -161,7 +196,6 @@ export default function PhoneEditForm({
} }
sx={{ flex: '1 1 240px', minWidth: 0 }} sx={{ flex: '1 1 240px', minWidth: 0 }}
placeholder={t('settingForm.verificationCode')} placeholder={t('settingForm.verificationCode')}
inputProps={{ dir: 'rtl', style: { textAlign: 'right' } }}
/> />
<Button <Button
@@ -169,26 +203,12 @@ export default function PhoneEditForm({
onClick={handleVerifyClick} onClick={handleVerifyClick}
disabled={isVerifying || verificationCode.length === 0} disabled={isVerifying || verificationCode.length === 0}
sx={{ sx={{
textTransform: 'none', minWidth: { xs: '100%', sm: 220 },
minWidth: { xs: '100%', sm: 170 },
bgcolor: 'primary.main', bgcolor: 'primary.main',
height: 56,
}} }}
> >
{isVerifying ? ( {isVerifying ? (
<Box <CircularProgress size={20} />
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') t('settingForm.checkCode')
)} )}
@@ -203,6 +223,6 @@ export default function PhoneEditForm({
> >
{t('settingForm.successfulChangePhone')} {t('settingForm.successfulChangePhone')}
</Toast> </Toast>
</Box> </>
); );
} }

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { type ReactElement, type ElementType } from 'react';
import { import {
Box, Box,
Button, Button,
@@ -11,15 +11,28 @@ import {
} from '@mui/material'; } from '@mui/material';
import Slide from '@mui/material/Slide'; import Slide from '@mui/material/Slide';
import type { TransitionProps } from '@mui/material/transitions'; import type { TransitionProps } from '@mui/material/transitions';
import { CloseSquare, Google, Apple } from 'iconsax-react'; import { CloseCircle } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
const MobileSlide = React.forwardRef(function MobileSlide( const MobileSlide = React.forwardRef(function MobileSlide(
props: TransitionProps & { children: React.ReactElement<any, any> }, props: TransitionProps & { children: ReactElement<unknown, ElementType> },
ref: React.Ref<unknown>, ref: React.Ref<unknown>,
) { ) {
return <Slide direction="up" ref={ref} {...props} />; return <Slide direction="up" ref={ref} {...props} />;
}); });
interface SocialMediaDialogProps {
open: boolean;
onClose: () => void;
t: (key: string) => string;
emailInput: string;
setEmailInput: (val: string) => void;
emailError: boolean;
setEmailError: (val: boolean) => void;
fullScreen: boolean;
computedMaxWidth: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
}
export default function SocialMediaDialog({ export default function SocialMediaDialog({
open, open,
onClose, onClose,
@@ -30,17 +43,7 @@ export default function SocialMediaDialog({
setEmailError, setEmailError,
fullScreen, fullScreen,
computedMaxWidth, computedMaxWidth,
}: { }: SocialMediaDialogProps) {
open: boolean;
onClose: () => void;
t: (key: string) => string;
emailInput: string;
setEmailInput: (val: string) => void;
emailError: boolean;
setEmailError: (val: boolean) => void;
fullScreen: boolean;
computedMaxWidth: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
}) {
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value; const value = e.target.value;
setEmailInput(value); setEmailInput(value);
@@ -59,30 +62,25 @@ export default function SocialMediaDialog({
TransitionComponent={fullScreen ? MobileSlide : undefined} TransitionComponent={fullScreen ? MobileSlide : undefined}
PaperProps={{ PaperProps={{
sx: { sx: {
m: { xs: 0, sm: 2 },
borderRadius: { xs: 0, sm: 2 }, borderRadius: { xs: 0, sm: 2 },
width: { xs: '100%', sm: 'auto' }, width: { xs: '100%', sm: '30%' },
height: { xs: '100%', sm: 'auto' }, height: { xs: '100%', sm: '43%' },
}, },
}} }}
> >
<DialogTitle <DialogTitle
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
fontWeight: 'bold', fontWeight: 'bold',
fontSize: '16px', fontSize: '16px',
gap: 1, gap: 1,
p: { xs: 1.5, sm: 2 }, p: { xs: 1.5, sm: 2 },
position: { xs: 'sticky', sm: 'static' }, bgcolor: 'background.default',
top: 0,
bgcolor: 'background.paper',
zIndex: 1,
}} }}
> >
<IconButton onClick={onClose} aria-label="Close"> <IconButton onClick={onClose} aria-label="Close">
<CloseSquare size={24} color="#82B1FF" /> <Icon Component={CloseCircle} size="medium" color="primary.main" />
</IconButton> </IconButton>
{t('settingForm.addEmailButton')} {t('settingForm.addEmailButton')}
</DialogTitle> </DialogTitle>
@@ -90,11 +88,9 @@ export default function SocialMediaDialog({
<DialogContent <DialogContent
dividers dividers
sx={{ sx={{
width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 2, gap: 2,
px: { xs: 2, sm: 3 },
py: { xs: 1.5, sm: 2 }, py: { xs: 1.5, sm: 2 },
}} }}
> >
@@ -128,59 +124,6 @@ export default function SocialMediaDialog({
> >
{t('settingForm.verificationCodeButton')} {t('settingForm.verificationCodeButton')}
</Button> </Button>
<Box sx={{ display: 'flex', alignItems: 'center', my: 2 }}>
<Box sx={{ flex: 1, height: 1, bgcolor: 'divider' }} />
<Typography sx={{ mx: 1, fontSize: 12, color: 'text.secondary' }}>
{t('settingForm.or')}
</Typography>
<Box sx={{ flex: 1, height: 1, bgcolor: 'divider' }} />
</Box>
<Box
sx={{
display: 'flex',
gap: 1,
flexDirection: { xs: 'column', sm: 'row' },
}}
>
<Button
fullWidth
sx={{
textTransform: 'none',
border: 2,
borderColor: 'primary.main',
color: 'primary.main',
borderRadius: 2,
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: 'primary.main',
color: 'primary.main',
borderRadius: 2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Apple size="20" color="black" style={{ marginInlineStart: 8 }} />
{t('settingForm.apple')}
</Button>
</Box>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@@ -1,16 +1,17 @@
import { Box, Typography, IconButton } from '@mui/material'; import { Box, Typography, IconButton } from '@mui/material';
import { Google, Apple, Sms, Trash } from 'iconsax-react'; import { Google, Sms, Trash } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
export default function SocialMediaList({ interface SocialMediaListProps {
emailList,
}: {
t: (key: string) => string; t: (key: string) => string;
emailList: readonly { emailList: readonly {
email: string; email: string;
provider: 'email' | 'google' | 'apple'; provider: 'email' | 'google' | 'apple';
time: string; time: string;
}[]; }[];
}) { }
export default function SocialMediaList({ emailList }: SocialMediaListProps) {
return ( return (
<Box sx={{ width: '100%', borderRadius: '8px', p: { xs: 1, sm: 2 } }}> <Box sx={{ width: '100%', borderRadius: '8px', p: { xs: 1, sm: 2 } }}>
{emailList.map((item, index) => ( {emailList.map((item, index) => (
@@ -33,15 +34,34 @@ export default function SocialMediaList({
minWidth: 0, minWidth: 0,
}} }}
> >
{item.provider === 'google' && ( <Box
<Google size="20" variant="Bold" color="#4285F4" /> sx={{
)} backgroundColor: 'primary.light',
{item.provider === 'apple' && ( width: 40,
<Apple size="20" variant="Bold" color="black" /> height: 40,
)} alignItems: 'center',
{item.provider === 'email' && ( justifyContent: 'center',
<Sms size="20" variant="Bold" color="#1976d2" /> display: 'flex',
)} borderRadius: 0.5,
}}
>
{item.provider === 'google' && (
<Icon
Component={Google}
size="medium"
color="primary.main"
variant="Bold"
/>
)}
{item.provider === 'email' && (
<Icon
Component={Sms}
size="medium"
variant="Bold"
color="primary.main"
/>
)}
</Box>
<Box sx={{ minWidth: 0 }}> <Box sx={{ minWidth: 0 }}>
<Typography variant="h6" noWrap> <Typography variant="h6" noWrap>
@@ -53,8 +73,8 @@ export default function SocialMediaList({
</Box> </Box>
</Box> </Box>
<IconButton size="small" aria-label="Delete"> <IconButton aria-label="Delete">
<Trash size="20" color="gray" variant="Outline" /> <Icon Component={Trash} size="medium" variant="Outline" />
</IconButton> </IconButton>
</Box> </Box>
))} ))}

View File

@@ -8,27 +8,35 @@ import {
ListItemText, ListItemText,
} from '@mui/material'; } from '@mui/material';
import { Message, Google, Apple, ArrowDown3 } from 'iconsax-react'; import { Message, Google, Apple, ArrowDown3 } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
interface SocialMediaMenuProps {
t: (key: string) => string;
onOpenDialog: () => void;
}
export default function SocialMediaMenu({ export default function SocialMediaMenu({
t, t,
onOpenDialog, onOpenDialog,
}: { }: SocialMediaMenuProps) {
t: (key: string) => string;
onOpenDialog: () => void;
}) {
const [anchor, setAnchor] = useState<null | HTMLElement>(null); const [anchor, setAnchor] = useState<null | HTMLElement>(null);
const openMenu = Boolean(anchor); const openMenu = Boolean(anchor);
const [open, setOpen] = useState(false);
const handleClickMenu = (e: React.MouseEvent<HTMLButtonElement>) => const handleClickMenu = (e: React.MouseEvent<HTMLButtonElement>) => {
setOpen(true);
setAnchor(e.currentTarget); setAnchor(e.currentTarget);
const handleCloseMenu = () => setAnchor(null); };
const handleCloseMenu = () => {
setOpen(false);
setAnchor(null);
};
return ( return (
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
justifyContent: 'flex-start', justifyContent: 'flex-start',
flexWrap: 'wrap',
gap: 1, gap: 1,
}} }}
> >
@@ -38,40 +46,28 @@ export default function SocialMediaMenu({
flexDirection: { xs: 'column', sm: 'row' }, flexDirection: { xs: 'column', sm: 'row' },
alignItems: { xs: 'stretch', sm: 'flex-start' }, alignItems: { xs: 'stretch', sm: 'flex-start' },
gap: 1, gap: 1,
width: '100%',
}} }}
> >
<Button <Button
onClick={handleClickMenu} onClick={handleClickMenu}
variant="outlined" variant="outlined"
sx={{ sx={{
width: '100%',
maxWidth: { sm: 187 },
textTransform: 'none',
border: '1px solid', border: '1px solid',
borderColor: 'primary.main', borderRadius: 1,
borderRadius: '8px',
color: 'primary.main',
fontSize: '14px',
px: 2,
py: 1,
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
backgroundColor: open ? 'primary.light' : 'primary.default',
}} }}
> >
<Box <Box component="span">{t('settingForm.addEmailOrSocialButton')}</Box>
component="span" <Icon
sx={{ Component={ArrowDown3}
overflow: 'hidden', size="medium"
textOverflow: 'ellipsis', color="primary.main"
direction: 'rtl', variant={open ? 'Bold' : 'Outline'}
}} />
>
{t('settingForm.addEmailOrSocialButton')}
</Box>
<ArrowDown3 size="20" color="#2979FF" />
</Button> </Button>
</Box> </Box>
@@ -81,7 +77,7 @@ export default function SocialMediaMenu({
onClose={handleCloseMenu} onClose={handleCloseMenu}
PaperProps={{ PaperProps={{
sx: { sx: {
minWidth: 240, minWidth: 187,
maxWidth: '90vw', maxWidth: '90vw',
}, },
}} }}
@@ -95,19 +91,19 @@ export default function SocialMediaMenu({
}} }}
> >
<ListItemIcon> <ListItemIcon>
<Message size={20} color="black" /> <Icon Component={Message} size="medium" color="primary.main" />
</ListItemIcon> </ListItemIcon>
<ListItemText>{t('settingForm.email')}</ListItemText> <ListItemText>{t('settingForm.email')}</ListItemText>
</MenuItem> </MenuItem>
<MenuItem> <MenuItem>
<ListItemIcon> <ListItemIcon>
<Google size={20} color="#4285F4" /> <Icon Component={Google} size="medium" color="primary.main" />
</ListItemIcon> </ListItemIcon>
<ListItemText>{t('settingForm.google')}</ListItemText> <ListItemText>{t('settingForm.google')}</ListItemText>
</MenuItem> </MenuItem>
<MenuItem> <MenuItem>
<ListItemIcon> <ListItemIcon>
<Apple size={20} color="black" /> <Icon Component={Apple} size="medium" color="primary.main" />
</ListItemIcon> </ListItemIcon>
<ListItemText>{t('settingForm.apple')}</ListItemText> <ListItemText>{t('settingForm.apple')}</ListItemText>
</MenuItem> </MenuItem>

View File

@@ -0,0 +1,13 @@
export enum Gender {
Male = 'male',
Female = 'female',
None = '',
}
export interface InfoRowData {
firstName: string;
lastName: string;
nationalCode: string;
country: string;
gender: Gender;
}

View File

@@ -27,6 +27,15 @@ export const CustomThemeProvider: React.FC<{ children: React.ReactNode }> = ({
}, },
spacing: 8, spacing: 8,
typography: typography, typography: typography,
components: {
MuiButton: {
defaultProps: {
sx: {
textTransform: 'none',
},
},
},
},
}); });
}, [i18n]); }, [i18n]);