diff --git a/public/locales/en/activeDevices.json b/public/locales/en/activeDevices.json
new file mode 100644
index 0000000..917b53a
--- /dev/null
+++ b/public/locales/en/activeDevices.json
@@ -0,0 +1,9 @@
+{
+ "active": {
+ "activeDevices": "Active devices",
+ "activeDevicesCaption": "Watch and manage all your active devices",
+ "deletDevicesButton": "Remove rest of the devices",
+ "deleteDevice": "Remove device",
+ "currentDevice": "Current device"
+ }
+}
diff --git a/public/locales/en/security.json b/public/locales/en/security.json
new file mode 100644
index 0000000..920f3fe
--- /dev/null
+++ b/public/locales/en/security.json
@@ -0,0 +1,17 @@
+{
+ "securityForm": {
+ "password": "Password",
+ "determinePassword": "Log in to your Harmony account more easily by setting a strong password.",
+ "addPassword": "Add password",
+ "notDeterminedPassword": "You have not set a password for this account yet.",
+ "newPassword": "New password",
+ "confirmPassword": "Confirm password",
+ "confirm": "Confirm",
+ "hasNumber": "Contains number",
+ "hasMinLength": "at least 8 character",
+ "hasUpperAndLower": "Contains a lowercase and uppercase letter",
+ "hasSpecialChar": "Contains sign (!@#$%^&*)",
+ "notCompatibility": "Confirm password is not the same as password.",
+ "alertSuccess": "Password has successfully changed"
+ }
+}
diff --git a/public/locales/en/setting.json b/public/locales/en/setting.json
new file mode 100644
index 0000000..7024be4
--- /dev/null
+++ b/public/locales/en/setting.json
@@ -0,0 +1,14 @@
+{
+ "settings": {
+ "title": "Base settings",
+ "description": "Change your base settings",
+ "editButton": "Edit",
+ "rejectButton": "Reject",
+ "saveButton": "Save",
+ "theme": "Theme and color",
+ "light": "Light",
+ "dark": "Dark",
+ "language": "زبان/language",
+ "calendar": "Calendar and date format"
+ }
+}
diff --git a/public/locales/fa/activeDevices.json b/public/locales/fa/activeDevices.json
new file mode 100644
index 0000000..929b2dd
--- /dev/null
+++ b/public/locales/fa/activeDevices.json
@@ -0,0 +1,9 @@
+{
+ "active": {
+ "activeDevices": "نشست های فعال",
+ "activeDevicesCaption": "مشاهده و مدیریت تمام نشست های فعال شما",
+ "deletDevicesButton": "حذف بقیه نشست ها",
+ "deleteDevice": "حذف نشست",
+ "currentDevice": "دستگاه فعلی"
+ }
+}
diff --git a/public/locales/fa/setting.json b/public/locales/fa/setting.json
new file mode 100644
index 0000000..948eb7a
--- /dev/null
+++ b/public/locales/fa/setting.json
@@ -0,0 +1,14 @@
+{
+ "settings": {
+ "title": "تنظیمات پایه",
+ "description": "تنظیمات پایهای حساب خود را تغییر دهید",
+ "editButton": "ویرایش",
+ "rejectButton": "لغو",
+ "saveButton": "ذخیره",
+ "theme": "تم و رنگ",
+ "light": "روشن",
+ "dark": "تاریک",
+ "language": "زبان/language",
+ "calendar": "فرمت تقویم و تاریخ"
+ }
+}
diff --git a/src/App.tsx b/src/App.tsx
index 43fe290..bd3de8a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -9,7 +9,9 @@ import {
import './App.css';
import { useTranslation } from 'react-i18next';
import { LanguageManager } from './components/LanguageManager';
-
+import { UserSecurity } from './features/profile/components/security/UserSecurity';
+import { ActiveDevices } from './features/profile/components/activeDevices/ActiveDevices';
+import { Setting } from './features/profile/components/setting/Setting';
function App() {
const { t } = useTranslation();
@@ -17,7 +19,10 @@ function App() {
<>
-
+
+
+
+ {/*
{t('helloWorld')}
-
+
*/}
>
);
}
@@ -48,6 +53,7 @@ function App() {
export default App;
import { Button } from '@mui/material';
+import { Avalanche } from 'iconsax-react';
export const ThemeToggleButton = () => {
const { mode, setMode } = useColorScheme();
diff --git a/src/assets/logo.svg b/src/assets/logo.svg
new file mode 100644
index 0000000..6f53ef6
--- /dev/null
+++ b/src/assets/logo.svg
@@ -0,0 +1,30 @@
+
diff --git a/src/components/CardContainer.tsx b/src/components/CardContainer.tsx
new file mode 100644
index 0000000..798b25a
--- /dev/null
+++ b/src/components/CardContainer.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+
+export function CardContainer({
+ title,
+ subtitle,
+ action,
+ children,
+ highlighted,
+}: {
+ title: string;
+ subtitle: string;
+ action?: React.ReactNode;
+ children?: React.ReactNode;
+ highlighted?: boolean;
+}) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+ {action}
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx
new file mode 100644
index 0000000..dbba376
--- /dev/null
+++ b/src/components/Logo.tsx
@@ -0,0 +1,7 @@
+import LogoSvg from '@/assets/logo.svg';
+
+function Logo() {
+ return
;
+}
+
+export default Logo;
diff --git a/src/components/ThemToggle.tsx b/src/components/ThemToggle.tsx
new file mode 100644
index 0000000..22f0b1c
--- /dev/null
+++ b/src/components/ThemToggle.tsx
@@ -0,0 +1,68 @@
+import { ToggleButtonGroup, ToggleButton, Box } from '@mui/material';
+import { useColorScheme } from '@mui/material/styles';
+import { Sun1, Moon } from 'iconsax-react';
+import { useTranslation } from 'react-i18next';
+
+export const ThemeToggleButton = () => {
+ const { mode, setMode } = useColorScheme();
+ const { t } = useTranslation('setting');
+
+ const handleChange = (_: any, newMode: 'light' | 'dark' | null) => {
+ if (newMode !== null) {
+ setMode(newMode);
+ localStorage.setItem('theme', newMode);
+ }
+ };
+
+ return (
+
+
+
+
+ {t('settings.light')}
+
+
+
+
+ {t('settings.dark')}
+
+
+
+ );
+};
diff --git a/src/features/profile/components/UserSecurity.tsx b/src/features/profile/components/UserSecurity.tsx
deleted file mode 100644
index 887bf95..0000000
--- a/src/features/profile/components/UserSecurity.tsx
+++ /dev/null
@@ -1,322 +0,0 @@
-import {
- Box,
- Typography,
- Button,
- Dialog,
- DialogTitle,
- DialogContent,
- TextField,
- DialogActions,
- IconButton,
-} from '@mui/material';
-import { useTranslation } from 'react-i18next';
-import { useState, useEffect } from 'react';
-import { CloseCircle, Refresh } from 'iconsax-react';
-import { PasswordValidationItem } from './PasswordValidation';
-import { Toast } from '@/components/Toast';
-
-export function UserSecurity() {
- const { t } = useTranslation('security');
- const [open, setOpen] = useState(false);
- const [password, setPassword] = useState('');
- const [confirmPassword, setConfirmPassword] = useState('');
- const [showValidation, setShowValidation] = useState(false);
- const [showPasswordAlert, setShowPasswordAlert] = useState(false);
- const [changePassword, setChangePassword] = useState(false);
- const [loading, setLoading] = useState(false);
-
- const hasNumber = /\d/.test(password);
- const hasMinLength = password.length >= 8;
- const hasUpperAndLower = /[A-Z]/.test(password) && /[a-z]/.test(password);
- const hasSpecialChar = /[!@#$%^&*]/.test(password);
- const validPassword =
- hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar;
- const matchPassword = password === confirmPassword;
-
- const handleOpen = () => setOpen(true);
- const handleClose = () => setOpen(false);
- const handleShowAlert = () => {
- setLoading(true);
- setTimeout(() => {
- setLoading(false);
- setShowPasswordAlert(true);
- setChangePassword(true);
- handleClose();
- setPassword('');
- setConfirmPassword('');
- }, 1500);
- };
-
- useEffect(() => {
- if (password) {
- if (!validPassword) {
- setShowValidation(true);
- } else {
- const timer = setTimeout(() => setShowValidation(false), 1000);
- return () => clearTimeout(timer);
- }
- } else {
- setShowValidation(false);
- }
- }, [password, validPassword]);
-
- return (
-
-
-
-
-
- {t('securityForm.password')}
-
- {t('securityForm.determinePassword')}
-
-
-
-
-
-
- {changePassword ? (
-
- رمز عبور فعال است
-
- آخرین تغییر چند ثانیه پیش
-
-
- ) : (
-
-
- {t('securityForm.notDeterminedPassword')}
-
-
- )}
-
-
-
-
- setShowPasswordAlert(false)}
- >
- {t('securityForm.alertSuccess')}
-
-
-
-
- );
-}
diff --git a/src/features/profile/components/activeDevices/ActiveDevices.tsx b/src/features/profile/components/activeDevices/ActiveDevices.tsx
new file mode 100644
index 0000000..350bb2e
--- /dev/null
+++ b/src/features/profile/components/activeDevices/ActiveDevices.tsx
@@ -0,0 +1,175 @@
+import { Box, Typography, Button } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { DeviceMessage, Logout } from 'iconsax-react';
+import Logo from '@/components/Logo';
+
+export function ActiveDevices() {
+ const { t } = useTranslation('activeDevices');
+
+ const devices = [
+ {
+ id: 0,
+ timeAndDate: 'دقایقی پیش',
+ deviceModel: 'asus i5 24i',
+ ip: '192.168.1.1',
+ current: true,
+ },
+ {
+ id: 1,
+ timeAndDate: '۲۲:۱۳ - ۱۴۰۴/۰۹/۰۹',
+ deviceModel: 'Dell XPS 15',
+ ip: '89.165.23.12',
+ current: false,
+ },
+ {
+ id: 2,
+ timeAndDate: '۲۲:۱۳ - ۱۴۰۴/۰۹/۰۹',
+ deviceModel: 'Samsung Galaxy S22',
+ ip: '10.0.0.5',
+ current: false,
+ },
+ {
+ id: 3,
+ timeAndDate: '۲۲:۱۳ - ۱۴۰۴/۰۹/۰۹',
+ deviceModel: 'MacBook Pro 14-inch',
+ ip: '172.16.0.101',
+ current: false,
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+ {t('active.activeDevices')}
+
+ {t('active.activeDevicesCaption')}
+
+
+
+
+
+
+ {devices.map((device) => (
+
+
+ {device.timeAndDate}
+
+
+
+
+
+ {device.deviceModel}
+
+
+
+
+ {device.ip}
+
+
+
+ {device.current ? (
+
+ ) : null}
+
+
+
+ }
+ sx={{
+ width: '80%',
+ borderRadius: '15px',
+ border: '1px solid',
+ borderColor: 'error.main',
+ height: '30px',
+ whiteSpace: 'nowrap',
+ color: 'error.main',
+ }}
+ disabled={device.current}
+ >
+ {t('active.deleteDevice')}
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/features/profile/components/PasswordValidation.tsx b/src/features/profile/components/security/PasswordValidation.tsx
similarity index 100%
rename from src/features/profile/components/PasswordValidation.tsx
rename to src/features/profile/components/security/PasswordValidation.tsx
diff --git a/src/features/profile/components/security/UserSecurity.tsx b/src/features/profile/components/security/UserSecurity.tsx
new file mode 100644
index 0000000..ae794a4
--- /dev/null
+++ b/src/features/profile/components/security/UserSecurity.tsx
@@ -0,0 +1,324 @@
+import {
+ Box,
+ Typography,
+ Button,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ TextField,
+ DialogActions,
+ IconButton,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { useState, useEffect } from 'react';
+import { CloseCircle, Refresh } from 'iconsax-react';
+import { PasswordValidationItem } from './PasswordValidation';
+import { Toast } from '@/components/Toast';
+import Logo from '@/components/Logo';
+import { CardContainer } from '@/components/CardContainer';
+
+export function UserSecurity() {
+ const { t } = useTranslation('security');
+ const [open, setOpen] = useState(false);
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [showValidation, setShowValidation] = useState(false);
+ const [showPasswordAlert, setShowPasswordAlert] = useState(false);
+ const [changePassword, setChangePassword] = useState(false);
+ const [loading, setLoading] = useState(false);
+
+ const hasNumber = /\d/.test(password);
+ const hasMinLength = password.length >= 8;
+ const hasUpperAndLower = /[A-Z]/.test(password) && /[a-z]/.test(password);
+ const hasSpecialChar = /[!@#$%^&*]/.test(password);
+ const validPassword =
+ hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar;
+ const matchPassword = password === confirmPassword;
+
+ const handleOpen = () => setOpen(true);
+ const handleClose = () => setOpen(false);
+ const handleShowAlert = () => {
+ setLoading(true);
+ setTimeout(() => {
+ setLoading(false);
+ setShowPasswordAlert(true);
+ setChangePassword(true);
+ handleClose();
+ setPassword('');
+ setConfirmPassword('');
+ }, 1500);
+ };
+
+ useEffect(() => {
+ if (password) {
+ if (!validPassword) {
+ setShowValidation(true);
+ } else {
+ const timer = setTimeout(() => setShowValidation(false), 1000);
+ return () => clearTimeout(timer);
+ }
+ } else {
+ setShowValidation(false);
+ }
+ }, [password, validPassword]);
+
+ return (
+
+
+
+
+
+
+
+
+ {t('securityForm.addPassword')}
+
+ }
+ >
+
+ {changePassword ? (
+
+ رمز عبور فعال است
+
+ آخرین تغییر چند ثانیه پیش
+
+
+ ) : (
+
+
+ {t('securityForm.notDeterminedPassword')}
+
+
+ )}
+
+
+
+
+ setShowPasswordAlert(false)}
+ >
+ {t('securityForm.alertSuccess')}
+
+
+
+
+
+ );
+}
diff --git a/src/features/profile/components/setting/Setting.tsx b/src/features/profile/components/setting/Setting.tsx
new file mode 100644
index 0000000..6ed6926
--- /dev/null
+++ b/src/features/profile/components/setting/Setting.tsx
@@ -0,0 +1,202 @@
+import {
+ Box,
+ Typography,
+ Button,
+ useColorScheme,
+ TextField,
+ Autocomplete,
+} from '@mui/material';
+import { CardContainer } from '@/components/CardContainer';
+import { useTranslation } from 'react-i18next';
+import { useState } from 'react';
+import { ThemeToggleButton } from '@/components/ThemToggle';
+import { Languages, CountryFlag } from './data/Languages';
+import Logo from '@/components/Logo';
+
+export function Setting() {
+ const { t } = useTranslation('setting');
+ const [isEditing, setIsEditing] = useState(false);
+ const { mode } = useColorScheme();
+ const [selectedLang, setSelectedLang] = useState('fa');
+ const selectedLanguage = Languages.find((lang) => lang.code === selectedLang);
+ const calendar = ['میلادی', 'شمسی'];
+ const [selectedCalendar, setSelectedCalendar] = useState('شمسی');
+
+ const toggleEdit = () => {
+ setIsEditing((prev) => !prev);
+ };
+
+ return (
+
+
+
+
+
+
+
+ {isEditing && (
+
+ )}
+
+
+ }
+ />
+
+
+
+ {isEditing ? (
+
+ {t('settings.theme')}
+
+
+ ) : (
+ <>
+
+ {t('settings.theme')}
+
+
+ {mode === 'light'
+ ? t('settings.light')
+ : t('settings.dark')}
+
+ >
+ )}
+
+
+
+ {isEditing ? (
+ option.fa}
+ value={selectedLanguage || null}
+ onChange={(_, newValue) => {
+ if (newValue) setSelectedLang(newValue.code);
+ }}
+ renderOption={(props, option) => (
+
+
+
+ )}
+ renderInput={(params) => (
+
+ )}
+ />
+ ) : (
+ <>
+
+ {t('settings.language')}
+
+
+ >
+ )}
+
+
+
+
+ {isEditing ? (
+ {
+ if (newValue) setSelectedCalendar(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ sx={{ width: '337px' }}
+ />
+ ) : (
+ <>
+
+ {t('settings.calendar')}
+
+ {selectedCalendar}
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/src/features/profile/components/setting/data/Languages.tsx b/src/features/profile/components/setting/data/Languages.tsx
new file mode 100644
index 0000000..1c95f00
--- /dev/null
+++ b/src/features/profile/components/setting/data/Languages.tsx
@@ -0,0 +1,44 @@
+import { Box, Typography } from '@mui/material';
+
+export const Languages = [
+ { code: 'en', en: 'English', fa: 'انگلیسی' },
+ { code: 'fa', en: 'Persian (Farsi)', fa: 'فارسی' },
+ { code: 'ar', en: 'Arabic', fa: 'عربی' },
+ { code: 'fr', en: 'French', fa: 'فرانسوی' },
+ { code: 'de', en: 'German', fa: 'آلمانی' },
+ { code: 'es', en: 'Spanish', fa: 'اسپانیایی' },
+ { code: 'it', en: 'Italian', fa: 'ایتالیایی' },
+ { code: 'ru', en: 'Russian', fa: 'روسی' },
+ { code: 'zh', en: 'Chinese', fa: 'چینی' },
+ { code: 'ja', en: 'Japanese', fa: 'ژاپنی' },
+ { code: 'ko', en: 'Korean', fa: 'کرهای' },
+ { code: 'hi', en: 'Hindi', fa: 'هندی' },
+ { code: 'tr', en: 'Turkish', fa: 'ترکی استانبولی' },
+ { code: 'pt', en: 'Portuguese', fa: 'پرتغالی' },
+ { code: 'ur', en: 'Urdu', fa: 'اردو' },
+ { code: 'nl', en: 'Dutch', fa: 'هلندی' },
+ { code: 'sv', en: 'Swedish', fa: 'سوئدی' },
+ { code: 'pl', en: 'Polish', fa: 'لهستانی' },
+ { code: 'th', en: 'Thai', fa: 'تایلندی' },
+ { code: 'id', en: 'Indonesian', fa: 'اندونزیایی' },
+];
+
+export function CountryFlag({ country }: { country: string }) {
+ const lang = Languages.find((lang) => lang.code === country);
+ const countryCode = lang?.code || 'un';
+
+ const flagUrl = `https://flagcdn.com/w40/${countryCode}.png`;
+
+ return (
+
+
+ {lang ? lang.fa : 'نامشخص'}
+
+ );
+}