diff --git a/.env b/.env index e68eac3..c471687 100644 --- a/.env +++ b/.env @@ -1,7 +1,7 @@ VITE_TOKEN_NAME = 'dsc_token' VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token' -# VITE_BASE_URL = 'http://192.168.1.113:4001' -VITE_BASE_URL = 'https://api-dzone.danakcorp.com' +VITE_BASE_URL = 'http://192.168.1.113:4001' +# VITE_BASE_URL = 'https://api-dzone.danakcorp.com' # VITE_DANAK_BASE_URL ='http://192.168.1.113:4000' VITE_DANAK_BASE_URL ='https://api.danakcorp.com' VITE_SERVICE_ID = '7e3c2f08-b7e7-4402-ae5f-fea99cff56bd' diff --git a/src/config/api.ts b/src/config/api.ts new file mode 100644 index 0000000..bde6226 --- /dev/null +++ b/src/config/api.ts @@ -0,0 +1,33 @@ +import axios from "axios"; +import { SettingType } from "../pages/setting/types/Types"; + +const baseURL = import.meta.env.VITE_API_URL; + +const instance = axios.create({ + baseURL, + headers: { + "Content-Type": "application/json", + }, +}); + +// Add request interceptor to add auth token +instance.interceptors.request.use((config) => { + const token = localStorage.getItem("token"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +export const api = { + // Setting endpoints + getSetting: () => instance.get("/setting").then((res) => res.data), + updateSetting: (data: SettingType) => + instance.post("/setting", data).then((res) => res.data), + + // Domain management endpoints + setDomain: (data: { domain: string }) => + instance.post("/domain", data).then((res) => res.data), + verifyDomain: () => instance.post("/domain/verify").then((res) => res.data), + getDomainStatus: () => instance.get("/domain/status").then((res) => res.data), +}; diff --git a/src/langs/fa.json b/src/langs/fa.json index f6adbba..a3ee9de 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -596,7 +596,18 @@ "zarinpal_merchant_id": "شناسه مرچنت زرین پال", "logo": "لوگو", "file_required": "فایل الزامی است", - "update_success": "تنظیمات با موفقیت به روز شد" + "update_success": "تنظیمات با موفقیت به روز شد", + "set_domain": "تنظیم دامنه", + "verify_domain": "تایید دامنه", + "domain_status": "وضعیت دامنه", + "verification_status": "وضعیت تایید", + "connection_status": "وضعیت اتصال", + "domain_verified": "دامنه تایید شده", + "domain_not_verified": "دامنه تایید نشده", + "domain_set_success": "دامنه با موفقیت تنظیم شد", + "domain_verify_success": "دامنه با موفقیت تایید شد", + "setup_instructions": "دستورالعمل‌های راه‌اندازی", + "completed": "تکمیل شده" }, "wallet": { "increese_wallet": "افزایش موجودی حساب اعتباری", diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 175c87e..456701d 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -2,6 +2,7 @@ import { FC, useEffect, useState } from 'react' import { useWorkspaces } from './hooks/useHomeData' import { workspaceItem } from './types/HomeTypes' import moment from 'moment-jalaali' +import { toast } from 'react-toastify' const Home: FC = () => { @@ -12,6 +13,20 @@ const Home: FC = () => { setSelectedWorkspace(localStorage.getItem(import.meta.env.VITE_WORKSPACE_ID)) }, []) + useEffect(() => { + if (getWorkspaces.isError && getWorkspaces.error instanceof Error) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const error = getWorkspaces.error as any; + if (error.response?.status === 403) { + toast.error(error.response?.data?.error.message[0]) + localStorage.removeItem(import.meta.env.VITE_WORKSPACE_ID) + window.location.href = 'https://console.danakcorp.com' + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getWorkspaces.isError, getWorkspaces.data]) + + const handleChangeWorkspace = (id: string) => { localStorage.setItem(import.meta.env.VITE_WORKSPACE_ID, id) setSelectedWorkspace(id) diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index d5efaf0..ea61fca 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -5,22 +5,44 @@ import { KeySquare, Notification1 } from 'iconsax-react' import Input from '../../components/Input' import Button from '../../components/Button' import { useFormik } from 'formik' -import { SettingType } from './types/Types' +import { SettingType, ConnectionOption } from './types/Types' import * as yup from 'yup' import UploadBox from '../../components/UploadBox' import { toast } from 'react-toastify' import { useSingleUpload } from '../service/hooks/useServiceData' import { ErrorType } from '../../helpers/types' -import { useSettingData, useUpdateSetting } from './hooks/useSettingData' +import { useSettingData, useUpdateSetting, useSetDomain, useVerifyDomain, useGetDomainStatus } from './hooks/useSettingData' const Setting: FC = () => { - const { t } = useTranslation('global') const [activeTab, setActiveTab] = useState('setting') const [file, setFile] = useState(null) const singleUpload = useSingleUpload() const updateSetting = useUpdateSetting() + const setDomain = useSetDomain() + const verifyDomain = useVerifyDomain() const { data: setting } = useSettingData() + const { data: domainStatus, refetch: refetchDomainStatus } = useGetDomainStatus() + + // Add polling effect for domain status + useEffect(() => { + if (activeTab === 'domain') { + let intervalId: number; + + if (domainStatus?.data?.hasDomain && !domainStatus.data.domainPointsToServer) { + intervalId = window.setInterval(() => { + refetchDomainStatus(); + }, 5000); // Check every 5 seconds + } + + return () => { + if (intervalId) { + window.clearInterval(intervalId); + } + }; + } + }, [domainStatus?.data?.hasDomain, domainStatus?.data?.domainPointsToServer, refetchDomainStatus, activeTab]); + const formik = useFormik({ initialValues: { zarinpalMerchantId: '', @@ -60,6 +82,37 @@ const Setting: FC = () => { } }) + const domainFormik = useFormik({ + initialValues: { + domain: '' + }, + validationSchema: yup.object({ + domain: yup.string().required(t('errors.required')), + }), + onSubmit: async (values) => { + await setDomain.mutateAsync(values, { + onSuccess: () => { + toast.success(t('setting.domain_set_success')) + refetchDomainStatus() + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error.message[0]) + } + }) + } + }) + + const handleVerifyDomain = async () => { + await verifyDomain.mutateAsync(undefined, { + onSuccess: () => { + refetchDomainStatus() + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error.message[0]) + } + }) + } + useEffect(() => { if (setting?.data) { formik.setValues(setting?.data?.business) @@ -117,69 +170,174 @@ const Setting: FC = () => { : -
-
- {t('setting.change_password')} -
+ activeTab === 'domain' ? +
+ {/* Domain Input Section */} +
+
+ {t('setting.domain')} +
-
-
- - - +
+ -
-
-
- {t('setting.password_is')} + {/* Domain Status Section */} + {domainStatus?.data?.hasDomain && ( +
+
+ {t('setting.domain_status')} +
+ +
+ {/* Domain Info */} +
+
+ {t('setting.domain')}: +
+
+ {domainStatus.data.domain} +
+
+ + {/* Verification Status */} +
+
+
+ {t('setting.verification_status')}: +
+
+ {domainStatus.data.isVerified ? t('setting.domain_verified') : domainStatus.data.verificationStatus} +
+
+
+ + {/* Connection Status */} +
+
+
+ {t('setting.connection_status')}: +
+
+ {domainStatus.data.connectionStatus} +
+
+
+ + {!domainStatus.data.isVerified && ( +
-
    -
  • - {t('setting.min-8')} -
  • -
  • - {t('setting.mix_charachter_number')} -
  • -
  • - {t('setting.exsist_number')} -
  • -
  • - {t('setting.exsist_special_character')} -
  • -
-
- -
-
+ )}
- -
-
+ : + null } -
) } diff --git a/src/pages/setting/hooks/useSettingData.ts b/src/pages/setting/hooks/useSettingData.ts index 19649ab..d664692 100644 --- a/src/pages/setting/hooks/useSettingData.ts +++ b/src/pages/setting/hooks/useSettingData.ts @@ -1,6 +1,13 @@ import { useMutation, useQuery } from "@tanstack/react-query"; -import { getSetting, updateSetting } from "../service/Service"; +import { + getSetting, + updateSetting, + setDomain, + verifyDomain, + getDomainStatus, +} from "../service/Service"; import { SettingType } from "../types/Types"; + export const useSettingData = () => { return useQuery({ queryKey: ["setting"], @@ -10,6 +17,25 @@ export const useSettingData = () => { export const useUpdateSetting = () => { return useMutation({ - mutationFn: (params: SettingType) => updateSetting(params), + mutationFn: (variables: SettingType) => updateSetting(variables), + }); +}; + +export const useSetDomain = () => { + return useMutation({ + mutationFn: (variables: { domain: string }) => setDomain(variables), + }); +}; + +export const useVerifyDomain = () => { + return useMutation({ + mutationFn: () => verifyDomain(), + }); +}; + +export const useGetDomainStatus = () => { + return useQuery({ + queryKey: ["domain-status"], + queryFn: getDomainStatus, }); }; diff --git a/src/pages/setting/service/Service.ts b/src/pages/setting/service/Service.ts index 3bd60f2..ffa93ff 100644 --- a/src/pages/setting/service/Service.ts +++ b/src/pages/setting/service/Service.ts @@ -1,6 +1,7 @@ import axios from "../../../config/axios"; import { SettingType } from "../types/Types"; +// Setting endpoints export const getSetting = async () => { const { data } = await axios.get("/business/settings"); return data; @@ -10,3 +11,19 @@ export const updateSetting = async (params: SettingType) => { const { data } = await axios.patch("/business/settings", params); return data; }; + +// Domain management endpoints +export const setDomain = async (params: { domain: string }) => { + const { data } = await axios.post("/business/domain", params); + return data; +}; + +export const verifyDomain = async () => { + const { data } = await axios.post("/business/domain/verify"); + return data; +}; + +export const getDomainStatus = async () => { + const { data } = await axios.get("/business/domain/status"); + return data; +}; diff --git a/src/pages/setting/types/Types.ts b/src/pages/setting/types/Types.ts index c7a5022..eac57ff 100644 --- a/src/pages/setting/types/Types.ts +++ b/src/pages/setting/types/Types.ts @@ -2,3 +2,48 @@ export type SettingType = { zarinpalMerchantId: string; logoUrl: string; }; + +export type VerificationInstructions = { + title: string; + description: string; + host: string; + type: string; + value: string; + ttl: string; +}; + +export type ConnectionOption = { + title: string; + steps: string[]; +}; + +export type ConnectionInstructions = { + title: string; + description: string; + options: ConnectionOption[]; + troubleshooting: string[]; +}; + +export type GeneralInstructions = { + verificationEndpoint: string; + propagationNote: string; + troubleshooting: string[]; +}; + +export type SetupInstructions = { + verificationInstructions: VerificationInstructions; + connectionInstructions: ConnectionInstructions; + general: GeneralInstructions; +}; + +export type DomainStatus = { + hasDomain: boolean; + domain: string; + isVerified: boolean; + verifiedAt: string | null; + domainPointsToServer: boolean; + verificationStatus: string; + connectionStatus: string; + fullSetupComplete: boolean; + setupInstructions: SetupInstructions; +};