setting domain
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
+12
-1
@@ -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": "افزایش موجودی حساب اعتباری",
|
||||
|
||||
@@ -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)
|
||||
|
||||
+214
-56
@@ -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<string>('setting')
|
||||
const [file, setFile] = useState<File | null>(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<SettingType>({
|
||||
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 = () => {
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className='mt-10 bg-white p-8 rounded-3xl'>
|
||||
<div>
|
||||
{t('setting.change_password')}
|
||||
</div>
|
||||
activeTab === 'domain' ?
|
||||
<div className='mt-10 space-y-6'>
|
||||
{/* Domain Input Section */}
|
||||
<div className='bg-white p-8 rounded-3xl'>
|
||||
<div className='mb-6'>
|
||||
{t('setting.domain')}
|
||||
</div>
|
||||
|
||||
<div className='flex xl:flex-row flex-col xl:gap-10 gap-4'>
|
||||
<div className='flex-1'>
|
||||
<Input
|
||||
type='password'
|
||||
className='mt-6'
|
||||
placeholder={t('setting.current_password')}
|
||||
/>
|
||||
<Input
|
||||
type='password'
|
||||
className='mt-6'
|
||||
placeholder={t('setting.new_password')}
|
||||
/>
|
||||
<Input
|
||||
type='password'
|
||||
className='mt-6'
|
||||
placeholder={t('setting.reapeat_password')}
|
||||
/>
|
||||
<div className='flex flex-col gap-6'>
|
||||
<Input
|
||||
label={t('setting.domain')}
|
||||
{...domainFormik.getFieldProps('domain')}
|
||||
error_text={domainFormik.touched.domain && domainFormik.errors.domain ? domainFormik.errors.domain : undefined}
|
||||
/>
|
||||
|
||||
<div className='mt-9 xl:block hidden'>
|
||||
<Button
|
||||
label={t('setting.save_change')}
|
||||
className='w-32'
|
||||
label={t('setting.set_domain')}
|
||||
onClick={() => domainFormik.handleSubmit()}
|
||||
isLoading={setDomain.isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 xl:p-8 p-4 bg-[#EEF0F7] rounded-3xl text-sm'>
|
||||
<div>
|
||||
{t('setting.password_is')}
|
||||
{/* Domain Status Section */}
|
||||
{domainStatus?.data?.hasDomain && (
|
||||
<div className='bg-white p-8 rounded-3xl'>
|
||||
<div className='mb-6'>
|
||||
{t('setting.domain_status')}
|
||||
</div>
|
||||
|
||||
<div className='space-y-6'>
|
||||
{/* Domain Info */}
|
||||
<div className='flex items-center gap-4'>
|
||||
<div className='text-sm font-medium'>
|
||||
{t('setting.domain')}:
|
||||
</div>
|
||||
<div className='text-sm'>
|
||||
{domainStatus.data.domain}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Verification Status */}
|
||||
<div className='p-4 bg-gray-50 rounded-lg'>
|
||||
<div className='flex items-center gap-4 mb-2'>
|
||||
<div className='text-sm font-medium'>
|
||||
{t('setting.verification_status')}:
|
||||
</div>
|
||||
<div className={`text-sm ${domainStatus.data.isVerified ? 'text-green-500' : 'text-red-500'}`}>
|
||||
{domainStatus.data.isVerified ? t('setting.domain_verified') : domainStatus.data.verificationStatus}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connection Status */}
|
||||
<div className='p-4 bg-gray-50 rounded-lg'>
|
||||
<div className='flex items-center gap-4 mb-2'>
|
||||
<div className='text-sm font-medium'>
|
||||
{t('setting.connection_status')}:
|
||||
</div>
|
||||
<div className={`text-sm ${domainStatus.data.domainPointsToServer ? 'text-green-500' : 'text-red-500'}`}>
|
||||
{domainStatus.data.connectionStatus}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!domainStatus.data.isVerified && (
|
||||
<Button
|
||||
label={t('setting.verify_domain')}
|
||||
onClick={handleVerifyDomain}
|
||||
isLoading={verifyDomain.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{domainStatus.data.setupInstructions && (
|
||||
<div className='space-y-6'>
|
||||
{/* Verification Instructions */}
|
||||
{domainStatus.data.setupInstructions.verificationInstructions && (
|
||||
<div className='p-4 bg-gray-50 rounded-lg'>
|
||||
<div className='flex items-center gap-2 mb-2'>
|
||||
<div className='font-bold'>
|
||||
{domainStatus.data.setupInstructions.verificationInstructions.title}
|
||||
</div>
|
||||
{domainStatus.data.isVerified && (
|
||||
<div className='text-green-500 text-sm'>
|
||||
✓ {t('setting.completed')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='text-sm mb-4'>
|
||||
{domainStatus.data.setupInstructions.verificationInstructions.description}
|
||||
</div>
|
||||
<div className='bg-white p-3 rounded border text-sm'>
|
||||
<div>Host: {domainStatus.data.setupInstructions.verificationInstructions.host}</div>
|
||||
<div>Type: {domainStatus.data.setupInstructions.verificationInstructions.type}</div>
|
||||
<div>Value: {domainStatus.data.setupInstructions.verificationInstructions.value}</div>
|
||||
<div>TTL: {domainStatus.data.setupInstructions.verificationInstructions.ttl}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Connection Instructions */}
|
||||
{domainStatus.data.setupInstructions.connectionInstructions && (
|
||||
<div className='p-4 bg-gray-50 rounded-lg'>
|
||||
<div className='flex items-center gap-2 mb-2'>
|
||||
<div className='font-bold'>
|
||||
{domainStatus.data.setupInstructions.connectionInstructions.title}
|
||||
</div>
|
||||
{domainStatus.data.domainPointsToServer && (
|
||||
<div className='text-green-500 text-sm'>
|
||||
✓ {t('setting.completed')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='text-sm mb-4'>
|
||||
{domainStatus.data.setupInstructions.connectionInstructions.description}
|
||||
</div>
|
||||
<div className='space-y-4'>
|
||||
{domainStatus.data.setupInstructions.connectionInstructions.options.map((option: ConnectionOption, index: number) => (
|
||||
<div key={index} className='bg-white p-3 rounded border'>
|
||||
<div className='font-medium mb-2'>{option.title}</div>
|
||||
<ul className='list-disc list-inside space-y-1 text-sm'>
|
||||
{option.steps.map((step: string, stepIndex: number) => (
|
||||
<li key={stepIndex}>{step}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{domainStatus.data.setupInstructions.connectionInstructions.troubleshooting && (
|
||||
<div className='mt-4'>
|
||||
<div className='font-bold mb-2'>Troubleshooting:</div>
|
||||
<ul className='list-disc list-inside space-y-1 text-sm'>
|
||||
{domainStatus.data.setupInstructions.connectionInstructions.troubleshooting.map((item: string, index: number) => (
|
||||
<li key={index}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* General Instructions */}
|
||||
{domainStatus.data.setupInstructions.general && (
|
||||
<div className='p-4 bg-gray-50 rounded-lg'>
|
||||
<div className='text-sm mb-4'>
|
||||
{domainStatus.data.setupInstructions.general.propagationNote}
|
||||
</div>
|
||||
{domainStatus.data.setupInstructions.general.troubleshooting && (
|
||||
<div>
|
||||
<div className='font-medium mb-2'>Troubleshooting:</div>
|
||||
<ul className='list-disc list-inside space-y-1 text-sm'>
|
||||
{domainStatus.data.setupInstructions.general.troubleshooting.map((item: string, index: number) => (
|
||||
<li key={index}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ul className='list-disc ps-4 flex flex-col gap-2 text-xs mt-4'>
|
||||
<li>
|
||||
{t('setting.min-8')}
|
||||
</li>
|
||||
<li>
|
||||
{t('setting.mix_charachter_number')}
|
||||
</li>
|
||||
<li>
|
||||
{t('setting.exsist_number')}
|
||||
</li>
|
||||
<li>
|
||||
{t('setting.exsist_special_character')}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className=' xl:hidden block'>
|
||||
<Button
|
||||
label={t('setting.save_change')}
|
||||
className='w-32'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='h-14'></div>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user