fix: code styles
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"notDetermined": "تعیین نشده",
|
"notDetermined": "تعیین نشده",
|
||||||
"successfulChangePhone": "شماره تماس با موفقیت تغییر کرد",
|
"successfulChangePhone": "شماره تماس با موفقیت تغییر کرد",
|
||||||
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
|
"phoneNumberIsInvalid": "شماره وارد شده نامعتبر میباشد",
|
||||||
"thisFieldIsRequired": "این فیلد الزامی است"
|
"thisFieldIsRequired": "این فیلد الزامی است",
|
||||||
|
"changePicture": "تغییر تصویر"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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: '/',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}>
|
||||||
|
|||||||
@@ -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)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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}>
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
13
src/features/profile/types.ts
Normal file
13
src/features/profile/types.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user