feat: add sidebar and change the style of that
This commit is contained in:
2
.npmrc
2
.npmrc
@@ -1,2 +0,0 @@
|
|||||||
@rkheftan:registry=https://npm.pkg.github.com
|
|
||||||
//npm.pkg.github.com/:_authToken=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
|
|
||||||
1593
package-lock.json
generated
1593
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@
|
|||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/material": "^7.2.0",
|
"@mui/material": "^7.2.0",
|
||||||
"@mui/stylis-plugin-rtl": "^7.2.0",
|
"@mui/stylis-plugin-rtl": "^7.2.0",
|
||||||
"@rkheftan/harmony-ui": "^0.0.3",
|
"@rkheftan/harmony-ui": "^0.1.2",
|
||||||
"i18next": "^25.3.0",
|
"i18next": "^25.3.0",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
|||||||
14
public/locales/en/sideMap.json
Normal file
14
public/locales/en/sideMap.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"side": {
|
||||||
|
"account": "Account",
|
||||||
|
"personalInfo": "Personal information",
|
||||||
|
"phoneNumber": "Phone number",
|
||||||
|
"email": "Email",
|
||||||
|
"security": "Security",
|
||||||
|
"password": "Password",
|
||||||
|
"verifiedAddress": "Verified addresses",
|
||||||
|
"recentLogins": "Recent logins",
|
||||||
|
"activeDevices": "Active devices",
|
||||||
|
"settings": "Settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
public/locales/fa/sideMap.json
Normal file
14
public/locales/fa/sideMap.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"side": {
|
||||||
|
"account": "حساب کاربری",
|
||||||
|
"personalInfo": "اطلاعات شخصی",
|
||||||
|
"phoneNumber": "شماره تماس",
|
||||||
|
"email": "ایمیل",
|
||||||
|
"security": "امنیت",
|
||||||
|
"password": "رمز عبور",
|
||||||
|
"verifiedAddress": "آدرس های تایید شده",
|
||||||
|
"recentLogins": "ورود های اخیر",
|
||||||
|
"activeDevices": "دستگاه های فعال",
|
||||||
|
"settings": "تنظیمات"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/App.tsx
11
src/App.tsx
@@ -1,14 +1,14 @@
|
|||||||
import { CssBaseline, useColorScheme } from '@mui/material';
|
import { CssBaseline, useColorScheme } from '@mui/material';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { LanguageManager } from './components/LanguageManager';
|
import { LanguageManager } from './components/LanguageManager';
|
||||||
|
import { Settings } from './features/profile/routes/SettingPage';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<LanguageManager />
|
<LanguageManager />
|
||||||
|
<Settings />
|
||||||
{/* <div style={{ padding: '16px' }}>
|
{/* <div style={{ padding: '16px' }}>
|
||||||
<Typography variant="h3">{t('helloWorld')}</Typography>
|
<Typography variant="h3">{t('helloWorld')}</Typography>
|
||||||
<Box
|
<Box
|
||||||
@@ -39,6 +39,13 @@ function App() {
|
|||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
|
import { PersonalInformation } from './features/profile/components/userInformation/PersonalInformation';
|
||||||
|
import { PhoneNumber } from './features/profile/components/userInformation/PhoneNumber';
|
||||||
|
import { SocialMedia } from './features/profile/components/userInformation/SocialMedia';
|
||||||
|
import { PasswordSecurity } from './features/profile/components/security/PasswordSecurity';
|
||||||
|
import { RecentLogins } from './features/profile/components/security/RecentLogins';
|
||||||
|
import { ActiveDevices } from './features/profile/components/activeDevices/ActiveDevices';
|
||||||
|
import { Setting } from './features/profile/components/setting/Setting';
|
||||||
|
|
||||||
export const ThemeToggleButton = () => {
|
export const ThemeToggleButton = () => {
|
||||||
const { mode, setMode } = useColorScheme();
|
const { mode, setMode } = useColorScheme();
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
import {
|
|
||||||
createBrowserRouter,
|
|
||||||
RouterProvider,
|
|
||||||
Navigate,
|
|
||||||
Outlet,
|
|
||||||
useLocation,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import { SideNav, type NavItemConfig } from '@rkheftan/harmony-ui';
|
|
||||||
import {
|
|
||||||
Devices,
|
|
||||||
LocationTick,
|
|
||||||
Mobile,
|
|
||||||
PasswordCheck,
|
|
||||||
Personalcard,
|
|
||||||
ProfileCircle,
|
|
||||||
Setting as SettingIcon,
|
|
||||||
Shield,
|
|
||||||
Sms,
|
|
||||||
} from 'iconsax-react';
|
|
||||||
import { Box, Typography, useTheme, useMediaQuery } from '@mui/material';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { ActiveDevices } from './components/activeDevices/ActiveDevices';
|
|
||||||
import { Setting } from './components/setting/Setting';
|
|
||||||
import { RecentLogins } from './components/security/RecentLogins';
|
|
||||||
import { PasswordSecurity } from './components/security/PasswordSecurity';
|
|
||||||
|
|
||||||
interface DummyPageProp {
|
|
||||||
sections: { title: string; hash: string }[];
|
|
||||||
}
|
|
||||||
function DummyPage({ sections }: DummyPageProp) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{sections.map(({ title, hash }) => (
|
|
||||||
<div key={hash} id={hash} style={{ height: '50vh', margin: '3rem' }}>
|
|
||||||
<Box p={3}>
|
|
||||||
<Typography variant="h4">{title}</Typography>
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Header() {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: 84,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
px: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body2">محمدحسین برزهگر</Typography>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
09123456789
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Layout() {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMdUp = useMediaQuery(theme.breakpoints.up('md'));
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const navConfig: NavItemConfig[] = [
|
|
||||||
{
|
|
||||||
text: 'حساب کاربری',
|
|
||||||
icon: <ProfileCircle size={24} />,
|
|
||||||
path: '/profile',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
text: 'اطلاعات شخصی',
|
|
||||||
icon: <Personalcard size={24} />,
|
|
||||||
path: '/profile#info',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'شماره تماس',
|
|
||||||
icon: <Mobile size={24} />,
|
|
||||||
path: '/profile#contact-info',
|
|
||||||
},
|
|
||||||
{ text: 'ایمیل', icon: <Sms size={24} />, path: '/profile#email' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'امنیت',
|
|
||||||
icon: <Shield size={24} />,
|
|
||||||
path: '/security',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
text: 'رمز عبور',
|
|
||||||
icon: <PasswordCheck size={24} />,
|
|
||||||
path: '/security#password',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'آدرسهای تایید شده',
|
|
||||||
icon: <LocationTick size={24} />,
|
|
||||||
path: '/security#locations',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'ورودهای اخیر',
|
|
||||||
icon: <Devices size={24} />,
|
|
||||||
path: '/security#sessions',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ text: 'دستگاههای فعال', icon: <Devices size={24} />, path: '/devices' },
|
|
||||||
{ text: 'تنظیمات', icon: <SettingIcon size={24} />, path: '/setting' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box display="flex" flexDirection="column" minHeight="100vh">
|
|
||||||
<Box display="flex" flex={1} overflow="hidden">
|
|
||||||
<SideNav
|
|
||||||
navConfig={navConfig}
|
|
||||||
header={<Header />}
|
|
||||||
activePath={location.pathname + location.hash}
|
|
||||||
sideNavVariant={isMdUp ? 'full' : 'minimized'}
|
|
||||||
drawerWidth={274}
|
|
||||||
minimizedWidth={50}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
flex={1}
|
|
||||||
display="flex"
|
|
||||||
justifyContent="center"
|
|
||||||
px={{ xs: 2, sm: 3 }}
|
|
||||||
>
|
|
||||||
<Box width="100%" maxWidth={790}>
|
|
||||||
<Outlet />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const profileSections = [
|
|
||||||
{ title: 'اطلاعات شخصی', hash: 'info' },
|
|
||||||
{ title: 'شماره تماس', hash: 'contact-info' },
|
|
||||||
{ title: 'ایمیل', hash: 'email' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
element: <Layout />,
|
|
||||||
children: [
|
|
||||||
{ path: '/', element: <Navigate to="/profile" replace /> },
|
|
||||||
{ path: '/profile', element: <DummyPage sections={profileSections} /> },
|
|
||||||
{
|
|
||||||
path: '/security',
|
|
||||||
element: (
|
|
||||||
<>
|
|
||||||
<div id="password">
|
|
||||||
<PasswordSecurity />
|
|
||||||
</div>
|
|
||||||
<div id="locations"></div>
|
|
||||||
<div id="sessions">
|
|
||||||
<RecentLogins />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ path: '/devices', element: <ActiveDevices /> },
|
|
||||||
{ path: '/setting', element: <Setting /> },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function Settings() {
|
|
||||||
useTranslation();
|
|
||||||
return <RouterProvider router={router} />;
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Box, Button } from '@mui/material';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { CardContainer } from '@/components/CardContainer';
|
|
||||||
import { ProfileImage } from './personlInformation/ProfileImage';
|
|
||||||
import { InfoRowDisplay } from './personlInformation/InfoRowDisplay';
|
|
||||||
import { InfoRowEdit } from './personlInformation/InfoRowEdit';
|
|
||||||
|
|
||||||
export function PersonalInformation() {
|
|
||||||
const { t } = useTranslation('profileSetting');
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
|
||||||
const [gender, setGender] = useState('');
|
|
||||||
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const initialData = {
|
|
||||||
firstName: 'محمد حسین',
|
|
||||||
lastName: 'برزهگر',
|
|
||||||
country: 'قطر',
|
|
||||||
gender: 'مرد',
|
|
||||||
nationalCode: '',
|
|
||||||
};
|
|
||||||
const [data, setData] = useState(initialData);
|
|
||||||
|
|
||||||
const initials = `${data.firstName?.trim()[0] || ''}${data.lastName?.trim()[0] || ''}`;
|
|
||||||
|
|
||||||
const toggleEdit = () => {
|
|
||||||
setIsEditing((prev) => !prev);
|
|
||||||
if (isEditing) {
|
|
||||||
setData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
gender: gender === 'male' ? 'مرد' : gender === 'female' ? 'زن' : '',
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
setGender(
|
|
||||||
data.gender === 'مرد' ? 'male' : data.gender === 'زن' ? 'female' : '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CardContainer
|
|
||||||
title={t('settingForm.titlePersonalInfo')}
|
|
||||||
subtitle={t('settingForm.descriptionPersonalInfo')}
|
|
||||||
highlighted={isEditing}
|
|
||||||
action={
|
|
||||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
|
||||||
{isEditing && (
|
|
||||||
<Button
|
|
||||||
variant="text"
|
|
||||||
onClick={() => setIsEditing(false)}
|
|
||||||
size="large"
|
|
||||||
sx={{
|
|
||||||
color: 'primary.main',
|
|
||||||
textTransform: 'none',
|
|
||||||
width: { xs: '100%', sm: 'auto' },
|
|
||||||
fontSize: { xs: '0.85rem', sm: '1rem' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('settingForm.rejectButton')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
onClick={toggleEdit}
|
|
||||||
size="large"
|
|
||||||
variant="outlined"
|
|
||||||
sx={{
|
|
||||||
textTransform: 'none',
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: 'primary.main',
|
|
||||||
borderRadius: '4px',
|
|
||||||
bgcolor: isEditing ? 'primary.main' : 'background.paper',
|
|
||||||
color: isEditing ? 'primary.contrastText' : 'primary.main',
|
|
||||||
px: { xs: 2, sm: '22px' },
|
|
||||||
py: { xs: '6px', sm: '8px' },
|
|
||||||
width: { xs: '100%', sm: isEditing ? '85px' : '93px' },
|
|
||||||
fontSize: { xs: '0.9rem', sm: '1rem' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isEditing
|
|
||||||
? t('settingForm.saveButton')
|
|
||||||
: t('settingForm.editButton')}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
px: { xs: 2, sm: 3, md: 4 },
|
|
||||||
py: 2,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 2,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isEditing && (
|
|
||||||
<ProfileImage
|
|
||||||
initials={initials}
|
|
||||||
uploadedImageUrl={uploadedImageUrl}
|
|
||||||
onImageChange={(e) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () =>
|
|
||||||
setUploadedImageUrl(reader.result as string);
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isEditing ? (
|
|
||||||
<InfoRowEdit
|
|
||||||
data={data}
|
|
||||||
setData={setData}
|
|
||||||
gender={gender}
|
|
||||||
setGender={setGender}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<InfoRowDisplay
|
|
||||||
data={data}
|
|
||||||
uploadedImageUrl={uploadedImageUrl}
|
|
||||||
initials={initials}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</CardContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import { Box, Typography, Button } 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 Logo from '@/components/Logo';
|
||||||
|
import { CardContainer } from '@/components/CardContainer';
|
||||||
|
|
||||||
export function ActiveDevices() {
|
export function ActiveDevices() {
|
||||||
const { t } = useTranslation('activeDevices');
|
const { t } = useTranslation('activeDevices');
|
||||||
@@ -40,43 +41,29 @@ export function ActiveDevices() {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: 'background.paper',
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: '834px',
|
|
||||||
mx: 'auto',
|
mx: 'auto',
|
||||||
px: { xs: 2, sm: 3 },
|
backgroundColor: 'background.paper',
|
||||||
py: 4,
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-start',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
py: 2,
|
py: 2,
|
||||||
|
height: 84,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo />
|
<Logo />
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ width: '100%', height: '1px', backgroundColor: 'divider' }} />
|
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
|
||||||
<Box sx={{ px: { xs: 2, sm: 3 } }}>
|
<CardContainer
|
||||||
<Box
|
title={t('active.activeDevices')}
|
||||||
sx={{
|
subtitle={t('active.activeDevicesCaption')}
|
||||||
display: 'flex',
|
action={
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
py: 1,
|
|
||||||
mb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Typography variant="h6">{t('active.activeDevices')}</Typography>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
{t('active.activeDevicesCaption')}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Button
|
<Button
|
||||||
|
variant="outlined"
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
borderColor: 'error.main',
|
borderColor: 'error.main',
|
||||||
@@ -87,31 +74,40 @@ export function ActiveDevices() {
|
|||||||
>
|
>
|
||||||
{t('active.deletDevicesButton')}
|
{t('active.deletDevicesButton')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 3, md: 4 },
|
||||||
|
py: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ width: '100%', maxWidth: '754px', px: 4 }}>
|
||||||
{devices.map((device) => (
|
{devices.map((device) => (
|
||||||
<Box
|
<Box
|
||||||
key={device.id}
|
key={device.id}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
py: 1,
|
gap: 1,
|
||||||
width: '690px',
|
height: 50,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ width: { xs: '100%', sm: '138px' } }}
|
sx={{ width: { xs: '100%', sm: '172.5px' } }}
|
||||||
>
|
>
|
||||||
{device.timeAndDate}
|
{device.timeAndDate}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: { xs: '100%', sm: '138px' },
|
width: { xs: '100%', sm: '172.5px' },
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -124,17 +120,15 @@ export function ActiveDevices() {
|
|||||||
{device.deviceModel}
|
{device.deviceModel}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ width: { xs: '100%', sm: '138px' } }}
|
sx={{ width: { xs: '100%', sm: '172.5px' } }}
|
||||||
>
|
>
|
||||||
{device.ip}
|
{device.ip}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: { xs: '100%', sm: '138px' },
|
width: { xs: '100%', sm: '172.5px' },
|
||||||
textAlign: { xs: 'left', sm: 'center' },
|
textAlign: { xs: 'left', sm: 'center' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -148,7 +142,7 @@ export function ActiveDevices() {
|
|||||||
height: '30px',
|
height: '30px',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
color: 'success.main',
|
color: 'success.main',
|
||||||
width: '75%',
|
width: '93px',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -156,10 +150,9 @@ export function ActiveDevices() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: { xs: '100%', sm: '138px' },
|
width: { xs: '100%', sm: '172.5px' },
|
||||||
textAlign: { xs: 'left', sm: 'center' },
|
textAlign: { xs: 'left', sm: 'center' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -193,5 +186,7 @@ export function ActiveDevices() {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</CardContainer>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { CloseCircle, Refresh } from 'iconsax-react';
|
import { CloseSquare, Refresh } from 'iconsax-react';
|
||||||
import { PasswordValidationItem } from './PasswordValidation';
|
import { PasswordValidationItem } from './PasswordValidation';
|
||||||
import { Toast } from '@/components/Toast';
|
import { Toast } from '@/components/Toast';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
@@ -67,30 +67,18 @@ export function PasswordSecurity() {
|
|||||||
}, [password, validPassword]);
|
}, [password, validPassword]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ overflowX: 'hidden', px: { xs: 2, sm: 3 } }}>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: 'background.paper',
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: '796px',
|
|
||||||
mx: 'auto',
|
mx: 'auto',
|
||||||
px: { xs: 1, sm: 2 },
|
backgroundColor: 'background.paper',
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-start',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
|
||||||
py: 2,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', py: 2, height: 84 }}>
|
||||||
<Logo />
|
<Logo />
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
|
||||||
sx={{ width: '100%', height: '1px', backgroundColor: 'divider' }}
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<CardContainer
|
<CardContainer
|
||||||
title={t('securityForm.password')}
|
title={t('securityForm.password')}
|
||||||
subtitle={t('securityForm.determinePassword')}
|
subtitle={t('securityForm.determinePassword')}
|
||||||
@@ -109,6 +97,17 @@ export function PasswordSecurity() {
|
|||||||
{t('securityForm.addPassword')}
|
{t('securityForm.addPassword')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 3, md: 4 },
|
||||||
|
py: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ height: 'auto', py: { xs: 3, sm: 4 } }}>
|
<Box sx={{ height: 'auto', py: { xs: 3, sm: 4 } }}>
|
||||||
{changePassword ? (
|
{changePassword ? (
|
||||||
@@ -143,7 +142,7 @@ export function PasswordSecurity() {
|
|||||||
<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" />
|
<CloseSquare size={24} color="#82B1FF" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
{t('securityForm.addPassword')}
|
{t('securityForm.addPassword')}
|
||||||
@@ -213,9 +212,7 @@ export function PasswordSecurity() {
|
|||||||
sx={{ maxWidth: '364px', height: 48, textTransform: 'none' }}
|
sx={{ maxWidth: '364px', height: 48, textTransform: 'none' }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleShowAlert}
|
onClick={handleShowAlert}
|
||||||
disabled={
|
disabled={!password || password !== confirmPassword || loading}
|
||||||
!password || password !== confirmPassword || loading
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Box
|
<Box
|
||||||
@@ -245,9 +242,8 @@ export function PasswordSecurity() {
|
|||||||
>
|
>
|
||||||
{t('securityForm.alertSuccess')}
|
{t('securityForm.alertSuccess')}
|
||||||
</Toast>
|
</Toast>
|
||||||
|
</Box>
|
||||||
</CardContainer>
|
</CardContainer>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Box, Typography, Button } from '@mui/material';
|
import { Box, Typography, Button } from '@mui/material';
|
||||||
import { CardContainer } from '@/components/CardContainer';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Logo from '@/components/Logo';
|
||||||
|
import { CardContainer } from '@/components/CardContainer';
|
||||||
|
|
||||||
export function RecentLogins() {
|
export function RecentLogins() {
|
||||||
const { t } = useTranslation('security');
|
const { t } = useTranslation('security');
|
||||||
@@ -22,27 +23,34 @@ export function RecentLogins() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ overflowX: 'hidden', px: { xs: 2, sm: 3 } }}>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: 'background.paper',
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: '796px',
|
|
||||||
mx: 'auto',
|
mx: 'auto',
|
||||||
px: { xs: 1, sm: 2 },
|
backgroundColor: 'background.paper',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
|
||||||
<CardContainer
|
<CardContainer
|
||||||
title={t('securityForm.recentLogins')}
|
title={t('securityForm.recentLogins')}
|
||||||
subtitle={t('securityForm.description')}
|
subtitle={t('securityForm.description')}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 3, md: 4 },
|
||||||
|
py: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ width: '100%', maxWidth: '754px', px: 4 }}>
|
<Box sx={{ width: '100%', maxWidth: '754px', px: 4 }}>
|
||||||
{data.map((d) => (
|
{data.map((d) => (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
// flexWrap: 'wrap',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 1,
|
gap: 1,
|
||||||
height: '50px',
|
height: '50px',
|
||||||
@@ -68,7 +76,7 @@ export function RecentLogins() {
|
|||||||
{d.ip}
|
{d.ip}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ width: { xs: '100%', sm: '172.5px' } }}>
|
<Box sx={{ width: { xs: '100%', sm: '172.5px' } }}>
|
||||||
{d.current ? (
|
{d.current && (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -84,16 +92,13 @@ export function RecentLogins() {
|
|||||||
>
|
>
|
||||||
{t('securityForm.currentDevice')}
|
{t('securityForm.currentDevice')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
|
||||||
<Typography></Typography>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
</CardContainer>
|
</CardContainer>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Box } from '@mui/material';
|
|||||||
|
|
||||||
export function Security() {
|
export function Security() {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ backgroundColor: 'background.paper' }}>
|
<Box sx={{ backgroundColor: 'background.paper', height: '730px' }}>
|
||||||
<PasswordSecurity />
|
<PasswordSecurity />
|
||||||
<RecentLogins />
|
<RecentLogins />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -21,20 +21,19 @@ export function Setting() {
|
|||||||
);
|
);
|
||||||
const [draftLanguage, setDraftLanguage] = useState<string>(savedLanguage);
|
const [draftLanguage, setDraftLanguage] = useState<string>(savedLanguage);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [selectedCalendar, setSelectedCalendar] = useState<string>(
|
||||||
|
t('settings.solar'),
|
||||||
|
);
|
||||||
|
|
||||||
const languageOptions = [
|
const languageOptions = [
|
||||||
{ code: 'en', label: 'English' },
|
{ code: 'en', label: 'English' },
|
||||||
{ code: 'fa', label: 'فارسی' },
|
{ code: 'fa', label: 'فارسی' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const calendarOptions = [
|
const calendarOptions = [
|
||||||
t('settings.christian'),
|
t('settings.christian'),
|
||||||
t('settings.solar'),
|
t('settings.solar'),
|
||||||
t('settings.lunar'),
|
t('settings.lunar'),
|
||||||
];
|
];
|
||||||
const [selectedCalendar, setSelectedCalendar] = useState<string>(
|
|
||||||
t('settings.solar'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDraftLanguageChange = (
|
const handleDraftLanguageChange = (
|
||||||
_: any,
|
_: any,
|
||||||
@@ -58,16 +57,15 @@ export function Setting() {
|
|||||||
isEditing ? handleSave() : setIsEditing(true);
|
isEditing ? handleSave() : setIsEditing(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ px: { xs: 2, sm: 3 }, py: 4, bgcolor: 'background.default' }}>
|
<Box sx={{ backgroundColor: 'background.paper' }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: 790,
|
display: 'flex',
|
||||||
mx: 'auto',
|
alignItems: 'center',
|
||||||
bgcolor: 'background.paper',
|
py: 2,
|
||||||
px: { xs: 1, sm: 2 },
|
height: 84,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', py: 2 }}>
|
|
||||||
<Logo />
|
<Logo />
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
|
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
|
||||||
@@ -82,7 +80,12 @@ export function Setting() {
|
|||||||
variant="text"
|
variant="text"
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
size="large"
|
size="large"
|
||||||
sx={{ textTransform: 'none' }}
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
textTransform: 'none',
|
||||||
|
width: { xs: '100%', sm: 'auto' },
|
||||||
|
fontSize: { xs: '0.85rem', sm: '1rem' },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t('settings.rejectButton')}
|
{t('settings.rejectButton')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -91,15 +94,34 @@ export function Setting() {
|
|||||||
onClick={handleEditToggle}
|
onClick={handleEditToggle}
|
||||||
size="large"
|
size="large"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{ textTransform: 'none' }}
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'primary.main',
|
||||||
|
borderRadius: '4px',
|
||||||
|
bgcolor: isEditing ? 'primary.main' : 'background.paper',
|
||||||
|
color: isEditing ? 'primary.contrastText' : 'primary.main',
|
||||||
|
px: { xs: 2, sm: '22px' },
|
||||||
|
py: { xs: '6px', sm: '8px' },
|
||||||
|
width: { xs: '100%', sm: isEditing ? '85px' : '93px' },
|
||||||
|
fontSize: { xs: '0.9rem', sm: '1rem' },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isEditing
|
{isEditing ? t('settings.saveButton') : t('settings.editButton')}
|
||||||
? t('settings.saveButton')
|
|
||||||
: t('settings.editButton')}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 3, md: 4 },
|
||||||
|
py: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -108,7 +130,7 @@ export function Setting() {
|
|||||||
mt: 2,
|
mt: 2,
|
||||||
mx: 'auto',
|
mx: 'auto',
|
||||||
width: { xs: '100%', md: 700 },
|
width: { xs: '100%', md: 700 },
|
||||||
px: 4,
|
// px: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ flex: 1, maxWidth: { sm: 337 }, width: '100%' }}>
|
<Box sx={{ flex: 1, maxWidth: { sm: 337 }, width: '100%' }}>
|
||||||
@@ -123,7 +145,9 @@ export function Setting() {
|
|||||||
{t('settings.theme')}
|
{t('settings.theme')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
{mode === 'light' ? t('settings.light') : t('settings.dark')}
|
{mode === 'light'
|
||||||
|
? t('settings.light')
|
||||||
|
: t('settings.dark')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -134,7 +158,8 @@ export function Setting() {
|
|||||||
options={languageOptions}
|
options={languageOptions}
|
||||||
getOptionLabel={(o) => o.label}
|
getOptionLabel={(o) => o.label}
|
||||||
value={
|
value={
|
||||||
languageOptions.find((o) => o.code === draftLanguage) || null
|
languageOptions.find((o) => o.code === draftLanguage) ||
|
||||||
|
null
|
||||||
}
|
}
|
||||||
onChange={handleDraftLanguageChange}
|
onChange={handleDraftLanguageChange}
|
||||||
renderInput={(p) => (
|
renderInput={(p) => (
|
||||||
@@ -149,14 +174,16 @@ export function Setting() {
|
|||||||
{t('settings.language')}
|
{t('settings.language')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
{languageOptions.find((o) => o.code === savedLanguage)?.label}
|
{
|
||||||
|
languageOptions.find((o) => o.code === savedLanguage)
|
||||||
|
?.label
|
||||||
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ mt: 2, px: 4 }}>
|
<Box sx={{ mt: 2, maxWidth: { sm: 310 }, width: '100%' }}>
|
||||||
<Box sx={{ maxWidth: { sm: 310 }, width: '100%' }}>
|
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
options={calendarOptions}
|
options={calendarOptions}
|
||||||
@@ -178,7 +205,7 @@ export function Setting() {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</CardContainer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Box, Button } from '@mui/material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { CardContainer } from '@/components/CardContainer';
|
||||||
|
import { ProfileImage } from './personlInformation/ProfileImage';
|
||||||
|
import { InfoRowDisplay } from './personlInformation/InfoRowDisplay';
|
||||||
|
import { InfoRowEdit } from './personlInformation/InfoRowEdit';
|
||||||
|
import Logo from '@/components/Logo';
|
||||||
|
|
||||||
|
export function PersonalInformation() {
|
||||||
|
const { t } = useTranslation('profileSetting');
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [gender, setGender] = useState('');
|
||||||
|
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const initialData = {
|
||||||
|
firstName: 'محمد حسین',
|
||||||
|
lastName: 'برزهگر',
|
||||||
|
country: 'قطر',
|
||||||
|
gender: 'مرد',
|
||||||
|
nationalCode: '',
|
||||||
|
};
|
||||||
|
const [data, setData] = useState(initialData);
|
||||||
|
|
||||||
|
const initials = `${data.firstName?.trim()[0] || ''}${data.lastName?.trim()[0] || ''}`;
|
||||||
|
|
||||||
|
const toggleEdit = () => {
|
||||||
|
setIsEditing((prev) => !prev);
|
||||||
|
if (isEditing) {
|
||||||
|
setData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
gender: gender === 'male' ? 'مرد' : gender === 'female' ? 'زن' : '',
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setGender(
|
||||||
|
data.gender === 'مرد' ? 'male' : data.gender === 'زن' ? 'female' : '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ backgroundColor: 'background.paper' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
py: 2,
|
||||||
|
height: 84,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Logo />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ width: '100%', height: 1, bgcolor: 'divider' }} />
|
||||||
|
<CardContainer
|
||||||
|
title={t('settingForm.titlePersonalInfo')}
|
||||||
|
subtitle={t('settingForm.descriptionPersonalInfo')}
|
||||||
|
highlighted={isEditing}
|
||||||
|
action={
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
|
{isEditing && (
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
onClick={() => setIsEditing(false)}
|
||||||
|
size="large"
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main',
|
||||||
|
textTransform: 'none',
|
||||||
|
width: { xs: '100%', sm: 'auto' },
|
||||||
|
fontSize: { xs: '0.85rem', sm: '1rem' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('settingForm.rejectButton')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={toggleEdit}
|
||||||
|
size="large"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'primary.main',
|
||||||
|
borderRadius: '4px',
|
||||||
|
bgcolor: isEditing ? 'primary.main' : 'background.paper',
|
||||||
|
color: isEditing ? 'primary.contrastText' : 'primary.main',
|
||||||
|
px: { xs: 2, sm: '22px' },
|
||||||
|
py: { xs: '6px', sm: '8px' },
|
||||||
|
width: { xs: '100%', sm: isEditing ? '85px' : '93px' },
|
||||||
|
fontSize: { xs: '0.9rem', sm: '1rem' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEditing
|
||||||
|
? t('settingForm.saveButton')
|
||||||
|
: t('settingForm.editButton')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 3, md: 4 },
|
||||||
|
py: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEditing && (
|
||||||
|
<ProfileImage
|
||||||
|
initials={initials}
|
||||||
|
uploadedImageUrl={uploadedImageUrl}
|
||||||
|
onImageChange={(e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () =>
|
||||||
|
setUploadedImageUrl(reader.result as string);
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isEditing ? (
|
||||||
|
<InfoRowEdit
|
||||||
|
data={data}
|
||||||
|
setData={setData}
|
||||||
|
gender={gender}
|
||||||
|
setGender={setGender}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<InfoRowDisplay
|
||||||
|
data={data}
|
||||||
|
uploadedImageUrl={uploadedImageUrl}
|
||||||
|
initials={initials}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</CardContainer>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -222,8 +222,8 @@ export function SocialMedia() {
|
|||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton onClick={handleCloseDialog} size="small">
|
<IconButton onClick={handleCloseDialog}>
|
||||||
<CloseSquare size="24" color="#F5F5F5" />
|
<CloseSquare size={24} color="#82B1FF" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{t('settingForm.addEmailButton')}
|
{t('settingForm.addEmailButton')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
203
src/features/profile/routes/SettingPage.tsx
Normal file
203
src/features/profile/routes/SettingPage.tsx
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import {
|
||||||
|
createBrowserRouter,
|
||||||
|
RouterProvider,
|
||||||
|
Navigate,
|
||||||
|
Outlet,
|
||||||
|
useLocation,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
import { SideNav, type NavItemConfig } from '@rkheftan/harmony-ui';
|
||||||
|
import {
|
||||||
|
Devices,
|
||||||
|
LocationTick,
|
||||||
|
Mobile,
|
||||||
|
PasswordCheck,
|
||||||
|
Personalcard,
|
||||||
|
ProfileCircle,
|
||||||
|
Setting as SettingIcon,
|
||||||
|
Shield,
|
||||||
|
Sms,
|
||||||
|
} from 'iconsax-react';
|
||||||
|
import { Box, Typography, useTheme, useMediaQuery } from '@mui/material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { ActiveDevices } from '../components/activeDevices/ActiveDevices';
|
||||||
|
import { Setting } from '../components/setting/Setting';
|
||||||
|
import { RecentLogins } from '../components/security/RecentLogins';
|
||||||
|
import { PasswordSecurity } from '../components/security/PasswordSecurity';
|
||||||
|
import { PersonalInformation } from '../components/userInformation/PersonalInformation';
|
||||||
|
import { PhoneNumber } from '../components/userInformation/PhoneNumber';
|
||||||
|
import { SocialMedia } from '../components/userInformation/SocialMedia';
|
||||||
|
|
||||||
|
function Header() {
|
||||||
|
const headers = [{ name: 'محمدحسین برزه گر', phone: '09123456789' }];
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 84,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
px: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{headers.map((h) => (
|
||||||
|
<Box key={h.phone}>
|
||||||
|
<Typography variant="body2">{h.name}</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{h.phone}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Layout() {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMdUp = useMediaQuery(theme.breakpoints.up('md'));
|
||||||
|
const location = useLocation();
|
||||||
|
const { t } = useTranslation('sideMap');
|
||||||
|
|
||||||
|
const drawerWidth = 274;
|
||||||
|
const minimizedWidth = 64;
|
||||||
|
const navWidth = isMdUp ? drawerWidth : minimizedWidth;
|
||||||
|
const contentWidth = 790;
|
||||||
|
const totalWidth = navWidth + contentWidth;
|
||||||
|
|
||||||
|
const navConfig: NavItemConfig[] = [
|
||||||
|
{
|
||||||
|
text: t('side.account'),
|
||||||
|
icon: <ProfileCircle size={24} />,
|
||||||
|
path: '/profile',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: t('side.personalInfo'),
|
||||||
|
icon: <Personalcard size={24} />,
|
||||||
|
path: '/profile#info',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('side.phoneNumber'),
|
||||||
|
icon: <Mobile size={24} />,
|
||||||
|
path: '/profile#contact-info',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('side.email'),
|
||||||
|
icon: <Sms size={24} />,
|
||||||
|
path: '/profile#email',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('side.security'),
|
||||||
|
icon: <Shield size={24} />,
|
||||||
|
path: '/security',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: t('side.password'),
|
||||||
|
icon: <PasswordCheck size={24} />,
|
||||||
|
path: '/security#password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('side.verifiedAddress'),
|
||||||
|
icon: <LocationTick size={24} />,
|
||||||
|
path: '/security#locations',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('side.recentLogins'),
|
||||||
|
icon: <Devices size={24} />,
|
||||||
|
path: '/security#sessions',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('side.activeDevices'),
|
||||||
|
icon: <Devices size={24} />,
|
||||||
|
path: '/devices',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('side.settings'),
|
||||||
|
icon: <SettingIcon size={24} />,
|
||||||
|
path: '/setting',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box display="flex" flexDirection="column" minHeight="100vh">
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
width={totalWidth}
|
||||||
|
maxWidth="100%"
|
||||||
|
mx="auto"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
>
|
||||||
|
<Box position="relative" width={navWidth}>
|
||||||
|
<SideNav
|
||||||
|
navConfig={navConfig}
|
||||||
|
header={<Header />}
|
||||||
|
activePath={location.pathname + location.hash}
|
||||||
|
sideNavVariant={isMdUp ? 'full' : 'minimized'}
|
||||||
|
positioning="absolute"
|
||||||
|
// drawerWidth={drawerWidth}
|
||||||
|
// minimizedWidth={minimizedWidth}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
// flex={1}
|
||||||
|
maxWidth={contentWidth}
|
||||||
|
width="100%"
|
||||||
|
// px={{ xs: 2, sm: 3 }}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Layout />,
|
||||||
|
children: [
|
||||||
|
{ path: '/', element: <Navigate to="/profile" replace /> },
|
||||||
|
{
|
||||||
|
path: '/profile',
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<div id="info">
|
||||||
|
<PersonalInformation />
|
||||||
|
</div>
|
||||||
|
<div id="contact-info">
|
||||||
|
<PhoneNumber />
|
||||||
|
</div>
|
||||||
|
<div id="email">
|
||||||
|
<SocialMedia />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/security',
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<div id="password">
|
||||||
|
<PasswordSecurity />
|
||||||
|
</div>
|
||||||
|
<div id="locations"></div>
|
||||||
|
<div id="sessions">
|
||||||
|
<RecentLogins />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ path: '/devices', element: <ActiveDevices /> },
|
||||||
|
{ path: '/setting', element: <Setting /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function Settings() {
|
||||||
|
return <RouterProvider router={router} />;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user