diff --git a/src/langs/fa.json b/src/langs/fa.json
index d51405d..5caaf51 100644
--- a/src/langs/fa.json
+++ b/src/langs/fa.json
@@ -25,7 +25,8 @@
"invalid_email": "فرمت ایمیل نامعتبر است"
},
"common": {
- "close": "بستن"
+ "close": "بستن",
+ "loading": "در حال بارگذاری..."
},
"profile": {
"account_user": "حساب کاربری",
@@ -128,7 +129,28 @@
"INVOICE": "صورتحساب",
"ACCOUNT": "حساب",
"NOTIFICATION": "اعلان",
- "FINANCE": "مالی"
+ "FINANCE": "مالی",
+ "2fa": "دو مرحله ای",
+ "2fa_setup": "راهاندازی احراز هویت دو مرحلهای",
+ "2fa_description": "احراز هویت دو مرحلهای امنیت اکانت شما را افزایش میدهد",
+ "setup_2fa": "راهاندازی کد دو مرحلهای",
+ "scan_qr_code": "QR Code زیر را با برنامه Authenticator اسکن کنید",
+ "manual_entry": "یا این کد را به صورت دستی وارد کنید",
+ "enter_verification_code": "کد تایید 6 رقمی را وارد کنید",
+ "enable_2fa": "فعال سازی احراز هویت دو مرحلهای",
+ "2fa_enabled_successfully": "احراز هویت دو مرحلهای با موفقیت فعال شد",
+ "2fa_enabled_description": "اکانت شما اکنون محافظت اضافی دارد",
+ "invalid_code": "کد وارد شده نامعتبر است",
+ "invalid_code_length": "کد باید 6 رقم باشد",
+ "setup_failed": "خطا در راهاندازی. دوباره تلاش کنید",
+ "2fa_setup_description": "برای شروع فرآیند راهاندازی احراز هویت دو مرحلهای، روی دکمه زیر کلیک کنید",
+ "2fa_info_title": "راهنمای احراز هویت دو مرحلهای",
+ "2fa_info_description": "این قابلیت امنیت حساب شما را به شدت افزایش میدهد",
+ "2fa_step_1": "یک برنامه Authenticator مانند Google Authenticator نصب کنید",
+ "2fa_step_2": "QR Code را اسکن کنید یا کد را به صورت دستی وارد کنید",
+ "2fa_step_3": "کد 6 رقمی تولید شده توسط برنامه را وارد کنید",
+ "note": "توجه",
+ "2fa_note": "کد های پشتیبان خود را در مکانی امن نگهداری کنید"
},
"success": "عملیات با موفقیت انجام شد",
"attachment_select": "اضافه کردن فایل ضمیمه",
diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx
index 5768862..06fb497 100644
--- a/src/pages/setting/Setting.tsx
+++ b/src/pages/setting/Setting.tsx
@@ -5,6 +5,7 @@ import { KeySquare, Notification1 } from 'iconsax-react'
import { useGetSettings } from './hooks/useSettingData'
import Sms from './components/Sms'
import ChangePassword from './components/ChangePassword'
+import TwoFactor from './components/TwoFactor'
import TitleLine from '@/components/TitleLine'
import { SettingCategoryType, SettingItemType } from './types/SettingTypes'
@@ -33,6 +34,11 @@ const Setting: FC = () => {
label: t('setting.password'),
value: 'password'
},
+ {
+ icon: ,
+ label: t('setting.2fa'),
+ value: '2fa'
+ },
]}
active={activeTab}
onChange={setActiveTab}
@@ -90,8 +96,11 @@ const Setting: FC = () => {
- :
-
+ : activeTab === 'password' ?
+
+ : activeTab === '2fa' ?
+
+ : null
}
diff --git a/src/pages/setting/components/TwoFactor.tsx b/src/pages/setting/components/TwoFactor.tsx
new file mode 100644
index 0000000..4214d38
--- /dev/null
+++ b/src/pages/setting/components/TwoFactor.tsx
@@ -0,0 +1,212 @@
+import { FC, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useSetupTwoFactor, useEnableTwoFactor } from '../hooks/useSettingData'
+import Input from '../../../components/Input'
+import Button from '../../../components/Button'
+import { toast } from '../../../components/Toast'
+import { ErrorType } from '../../../helpers/types'
+import { TickCircle, Scan, Key } from 'iconsax-react'
+import { TwoFactorSetupResponseType } from '../types/SettingTypes'
+import { useGetProfile } from '@/pages/profile/hooks/useProfileData'
+
+const TwoFactor: FC = () => {
+
+ const { t } = useTranslation('global')
+ const { data: profile } = useGetProfile()
+ const [qrData, setQrData] = useState(null)
+ const [token, setToken] = useState('')
+ const [isEnabled, setIsEnabled] = useState(false)
+
+ const setupMutation = useSetupTwoFactor()
+ const enableMutation = useEnableTwoFactor()
+
+ const handleSetup = () => {
+ setupMutation.mutate(undefined, {
+ onSuccess: (data) => {
+ setQrData(data.data)
+ toast(data.data.message, 'success')
+ },
+ onError: (error: ErrorType) => {
+ const errorMessage = error.response?.data?.error?.message
+ const message = Array.isArray(errorMessage) ? errorMessage[0] : errorMessage
+ toast(message || t('setting.setup_failed'), 'error')
+ }
+ })
+ }
+
+ useEffect(() => {
+ if (profile?.data?.user?.is2FAEnabled) {
+ setIsEnabled(true)
+ }
+ }, [profile])
+
+ const handleEnable = () => {
+ if (token.length !== 6) {
+ toast(t('setting.invalid_code_length'), 'error')
+ return
+ }
+
+ enableMutation.mutate(token, {
+ onSuccess: (data) => {
+ setIsEnabled(true)
+ setQrData(null)
+ setToken('')
+ toast(data.message || t('setting.2fa_enabled_successfully'), 'success')
+ },
+ onError: (error: ErrorType) => {
+ const errorMessage = error.response?.data?.error?.message
+ const message = Array.isArray(errorMessage) ? errorMessage[0] : errorMessage
+ toast(message || t('setting.invalid_code'), 'error')
+ }
+ })
+ }
+
+ const handleTokenChange = (e: React.ChangeEvent) => {
+ const value = e.target.value.replace(/\D/g, '').slice(0, 6)
+ setToken(value)
+ }
+
+ if (isEnabled) {
+ return (
+
+
+
+
+
+
+ {t('setting.2fa_enabled_successfully')}
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ {t('setting.2fa_setup')}
+
+
+ {t('setting.2fa_description')}
+
+
+
+
+ {/* Setup Section */}
+
+ {!qrData ? (
+
+
+
+
+
+ {t('setting.2fa_setup_description')}
+
+
+
+ ) : (
+
+ {/* QR Code Section */}
+
+
+ {t('setting.scan_qr_code')}
+
+
+
+

+
+
+
+
{t('setting.manual_entry')}:
+
{qrData.seed}
+
+
+
+ {/* Token Input Section */}
+
+
+
+
+
+
+ )}
+
+
+ {/* Information Section */}
+
+
+
{t('setting.2fa_info_title')}
+
+ {t('setting.2fa_info_description')}
+
+
+
+
+
+
+ 1
+
+
{t('setting.2fa_step_1')}
+
+
+
+
+ 2
+
+
{t('setting.2fa_step_2')}
+
+
+
+
+ 3
+
+
{t('setting.2fa_step_3')}
+
+
+
+
+
+ {t('setting.note')}: {t('setting.2fa_note')}
+
+
+
+
+
+
+
+ )
+}
+
+export default TwoFactor
\ No newline at end of file
diff --git a/src/pages/setting/hooks/useSettingData.ts b/src/pages/setting/hooks/useSettingData.ts
index 00811b1..1c7bd17 100644
--- a/src/pages/setting/hooks/useSettingData.ts
+++ b/src/pages/setting/hooks/useSettingData.ts
@@ -1,6 +1,11 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import * as api from "../service/SettingService";
-import { ChangePasswordType, UpdateSettingType } from "../types/SettingTypes";
+import {
+ ChangePasswordType,
+ UpdateSettingType,
+ TwoFactorSetupResponseType,
+ TwoFactorEnableResponseType,
+} from "../types/SettingTypes";
export const useGetSettings = () => {
return useQuery({
@@ -21,3 +26,15 @@ export const useChangePassword = () => {
api.changePassword(variables),
});
};
+
+export const useSetupTwoFactor = () => {
+ return useMutation({
+ mutationFn: () => api.setupTwoFactor(),
+ });
+};
+
+export const useEnableTwoFactor = () => {
+ return useMutation({
+ mutationFn: (token: string) => api.enableTwoFactor(token),
+ });
+};
diff --git a/src/pages/setting/service/SettingService.ts b/src/pages/setting/service/SettingService.ts
index 5c4dd5c..847e0f0 100644
--- a/src/pages/setting/service/SettingService.ts
+++ b/src/pages/setting/service/SettingService.ts
@@ -1,5 +1,10 @@
import axios from "../../../config/axios";
-import { ChangePasswordType, UpdateSettingType } from "../types/SettingTypes";
+import {
+ ChangePasswordType,
+ UpdateSettingType,
+ TwoFactorSetupResponseType,
+ TwoFactorEnableResponseType,
+} from "../types/SettingTypes";
export const getSettings = async () => {
const { data } = await axios.get(`/settings`);
@@ -13,3 +18,15 @@ export const updateSettings = async (params: UpdateSettingType) => {
export const changePassword = async (params: ChangePasswordType) => {
await axios.patch(`/auth/change-password`, params);
};
+
+export const setupTwoFactor = async (): Promise => {
+ const { data } = await axios.post(`/auth/2fa/setup`);
+ return data;
+};
+
+export const enableTwoFactor = async (
+ token: string
+): Promise => {
+ const { data } = await axios.post(`/auth/2fa/enable`, { token });
+ return data;
+};
diff --git a/src/pages/setting/types/SettingTypes.ts b/src/pages/setting/types/SettingTypes.ts
index a8bd792..f669408 100644
--- a/src/pages/setting/types/SettingTypes.ts
+++ b/src/pages/setting/types/SettingTypes.ts
@@ -29,3 +29,23 @@ export type SettingsResponseType = {
settings: SettingCategoryType[];
};
};
+
+export type TwoFactorSetupResponseType = {
+ statusCode: number;
+ success: boolean;
+ data: {
+ message: string;
+ qr: string;
+ seed: string;
+ };
+};
+
+export type TwoFactorEnableResponseType = {
+ statusCode: number;
+ success: boolean;
+ message: string;
+};
+
+export type TwoFactorEnableType = {
+ token: string;
+};