address
This commit is contained in:
@@ -4,3 +4,5 @@ VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.1.134:4000'
|
||||
VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
|
||||
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
|
||||
|
||||
VITE_LOGIN_URL = 'https://console.danakcorp.com/auth/login'
|
||||
|
||||
+14
-11
@@ -1,13 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dmail Admin | داناک</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
<svg width="85" height="72" viewBox="0 0 85 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M47.97 0C39.21 0 20.6 0.0500073 20.18 0.0700073C28.87 8.77001 45.47 25.11 55.3 34.9C54.27 35.93 53.22 36.98 52.11 38.09C49.1 41.1 47.07 41.82 44.03 38.78C31.57 26.32 5.38 0.07 5.28 0H0V71.79C0 71.79 11.97 71.77 12.57 71.77V24.82C12.63 24.6 16.87 28.77 22.08 33.96L21.63 71.81C22.28 71.81 39.72 71.83 47.28 71.8C49.16 71.8 51.06 71.69 52.93 71.49C69.23 69.67 82.67 55.87 84.14 39.48C86.07 17.95 69.62 0 47.98 0H47.97ZM46.3 11.58C55.58 10.72 63.99 15.5 67.84 22.39C66.04 24.19 64.36 25.86 62.72 27.49C57.77 22.69 49.24 14.43 46.3 11.58ZM48.3 60.07C45.38 60.12 42.46 60.12 39.54 60.11H31.86V43.77C33.94 45.85 35.86 47.78 37.43 49.33C43.64 55.49 53.12 52.88 59.33 46.75C63.62 42.51 71.94 34.1 72.57 33.51C73.5 47.57 62.46 59.84 48.29 60.09L48.3 60.07Z" fill="black"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 876 B |
+1
-1
@@ -81,7 +81,7 @@ const queryClient = new QueryClient({
|
||||
// Clear tokens and redirect to login
|
||||
await removeToken();
|
||||
await removeRefreshToken();
|
||||
window.location.href = '/auth/login';
|
||||
window.location.href = import.meta.env.VITE_LOGIN_URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { FC, useState } from 'react'
|
||||
import DefaulModal from './DefaulModal'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from './Button'
|
||||
import Textarea from './Textarea'
|
||||
|
||||
type Props = {
|
||||
isLoading?: boolean,
|
||||
close: () => void,
|
||||
isOpen: boolean,
|
||||
onConfrim: (text?: string) => void,
|
||||
label?: string,
|
||||
isHasDescription?: boolean,
|
||||
title?: string
|
||||
}
|
||||
|
||||
const ModalConfrim: FC<Props> = (props: Props) => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
const [description, setDescription] = useState<string>('')
|
||||
|
||||
return (
|
||||
<DefaulModal
|
||||
open={props.isOpen}
|
||||
close={props.close}
|
||||
title_header={props.title ? props.title : t('confrim.subject')}
|
||||
isHeader
|
||||
>
|
||||
<div className='mt-6'>
|
||||
<div className='text-sm text-center'>
|
||||
{
|
||||
props.label ?
|
||||
props.label
|
||||
:
|
||||
t('confrim.content')
|
||||
}
|
||||
|
||||
{
|
||||
props.isHasDescription &&
|
||||
<div className='mt-4'>
|
||||
<Textarea
|
||||
placeholder={t('description')}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
value={description}
|
||||
className='bg-transparent border border-gray-500 rounded-xl w-[300px] p-2'
|
||||
label=''
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='flex gap-4 justify-center mt-10'>
|
||||
<Button
|
||||
label={t('confrim.yes')}
|
||||
onClick={() => props.onConfrim(props.isHasDescription ? description : undefined)}
|
||||
loading={props.isLoading}
|
||||
/>
|
||||
<Button
|
||||
label={t('confrim.cancel')}
|
||||
className='bg-transparent text-black border border-primary'
|
||||
onClick={props.close}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalConfrim
|
||||
@@ -101,6 +101,11 @@
|
||||
"smtp_port": "SMTP پورت",
|
||||
"username": "نام کاربری",
|
||||
"password": "پسورد",
|
||||
"setting_imap": "تنظیمات IMAP",
|
||||
"setting_imap_description": "تنظیمات IMAP خود را با دقت وارد کنید.",
|
||||
"imap_server": "IMAP سرور",
|
||||
"imap_port": "IMAP پورت",
|
||||
"encryption": "نوع رمزنگاری",
|
||||
"record_dns": "رکوردهای DNS",
|
||||
"record_dns_description": "در این قسمت میتوانید وضعیت رکوردهای DNS سرویس ایمیل خود را بررسی کنید.",
|
||||
"your_domain": "دامنه شما",
|
||||
@@ -166,5 +171,13 @@
|
||||
"answer": "پاسخ",
|
||||
"forward": "ارسال به دیگران",
|
||||
"message": "متن پبام"
|
||||
},
|
||||
"confrim": {
|
||||
"subject": "تایید عملیات",
|
||||
"content": "آیا از انجام این عملیات مطمئن هستید؟",
|
||||
"yes": "بله",
|
||||
"cancel": "انصراف",
|
||||
"delete_address_title": "حذف آدرس",
|
||||
"delete_address_message": "آیا از حذف آدرس {email} مطمئن هستید؟"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,44 +5,149 @@ import { ColumnType } from '@/components/types/TableTypes'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { TickCircle, Edit2, Trash } from 'iconsax-react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { AddressData, CreateAddressType } from './types/Types'
|
||||
import { AddressData, CreateAddressType, UpdateAddressType } from './types/Types'
|
||||
import { useGetDomains } from '../domain/hooks/useDomainData'
|
||||
import { yupResolver } from "@hookform/resolvers/yup"
|
||||
import * as yup from "yup"
|
||||
import { useCreateAddress, useGetAddress } from './hooks/useAddressData'
|
||||
import { useCreateAddress, useGetAddress, useUpdateAddress, useDeleteAddress } from './hooks/useAddressData'
|
||||
import { useGetSpace } from '../domain/hooks/useDomainData'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { ErrorType } from '@/helpers/types'
|
||||
import ProgressBarEmail from './components/ProgressBarEmail'
|
||||
import ToggleStatus from './components/ToggleStatus'
|
||||
|
||||
|
||||
import RowActionsDropdown, { RowActionItem } from '@/components/RowActionsDropdown'
|
||||
import ModalConfrim from '@/components/ModalConfrim'
|
||||
|
||||
const Address: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const schema = yup.object().shape({
|
||||
title: yup.string().required(t('errors.required')),
|
||||
username: yup.string().required(t('errors.required')),
|
||||
password: yup.string().required(t('errors.required')),
|
||||
domainId: yup.string().required(t('errors.required')),
|
||||
})
|
||||
|
||||
const [search, setSearch] = useState<string>('')
|
||||
const [status, setStatus] = useState<string>('all')
|
||||
const [currentPage, setCurrentPage] = useState<number>(1)
|
||||
const [selectedAddress, setSelectedAddress] = useState<AddressData | null>(null)
|
||||
const [isEditMode, setIsEditMode] = useState(false)
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
|
||||
const [addressToDelete, setAddressToDelete] = useState<AddressData | null>(null)
|
||||
|
||||
const { data: domain } = useGetDomains()
|
||||
const { data: spaceData } = useGetSpace()
|
||||
const { mutate: createAddress, isPending } = useCreateAddress()
|
||||
const { data: addresses, isPending: isPendingAddress } = useGetAddress(search, status)
|
||||
const { register, handleSubmit, formState: { errors }, setValue
|
||||
} = useForm<CreateAddressType>({
|
||||
resolver: yupResolver(schema),
|
||||
const { mutate: updateAddress, isPending: isUpdating } = useUpdateAddress()
|
||||
const { mutate: deleteAddress } = useDeleteAddress()
|
||||
const { data: addresses, isPending: isPendingAddress } = useGetAddress(search, status, currentPage, 10)
|
||||
|
||||
type FormData = CreateAddressType & { password?: string; quotaMB?: number }
|
||||
|
||||
const { register, handleSubmit, formState: { errors }, setValue, reset, setError, clearErrors
|
||||
} = useForm<FormData>({
|
||||
defaultValues: {
|
||||
domainId: domain?.data?.domain?.id,
|
||||
}
|
||||
})
|
||||
|
||||
const validateForm = (params: FormData): boolean => {
|
||||
clearErrors()
|
||||
let isValid = true
|
||||
|
||||
if (!params.title) {
|
||||
setError('title', { message: t('errors.required') })
|
||||
isValid = false
|
||||
}
|
||||
|
||||
if (!params.username) {
|
||||
setError('username', { message: t('errors.required') })
|
||||
isValid = false
|
||||
}
|
||||
|
||||
if (!isEditMode && !params.password) {
|
||||
setError('password', { message: t('errors.required') })
|
||||
isValid = false
|
||||
}
|
||||
|
||||
if (!params.domainId) {
|
||||
setError('domainId', { message: t('errors.required') })
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// اعتبارسنجی quota
|
||||
if (params.quotaMB) {
|
||||
const quotaInBytes = params.quotaMB * 1024 * 1024
|
||||
const remainingSpace = spaceData?.data?.quota?.totalInGB * 1024 * 1024 * 1024 - spaceData?.data?.quota?.usedInGB * 1024 * 1024 * 1024
|
||||
|
||||
if (quotaInBytes > remainingSpace) {
|
||||
setError('quotaMB', { message: 'فضای وارد شده بیش از فضای باقیمانده است' })
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
const handleEdit = (address: AddressData) => {
|
||||
setSelectedAddress(address)
|
||||
setIsEditMode(true)
|
||||
reset({
|
||||
title: address.title,
|
||||
username: address.displayName,
|
||||
password: '',
|
||||
domainId: address.domain.id,
|
||||
quotaMB: address.emailQuota ? Math.round(address.emailQuota / (1024 * 1024)) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (address: AddressData) => {
|
||||
setAddressToDelete(address)
|
||||
setIsDeleteModalOpen(true)
|
||||
}
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
if (addressToDelete) {
|
||||
deleteAddress(addressToDelete.id, {
|
||||
onSuccess: (data) => {
|
||||
toast(data?.data?.message || 'آدرس با موفقیت حذف شد', 'success')
|
||||
setIsDeleteModalOpen(false)
|
||||
setAddressToDelete(null)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0] || 'خطا در حذف', 'error')
|
||||
setIsDeleteModalOpen(false)
|
||||
setAddressToDelete(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleCloseDeleteModal = () => {
|
||||
setIsDeleteModalOpen(false)
|
||||
setAddressToDelete(null)
|
||||
}
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
setSelectedAddress(null)
|
||||
setIsEditMode(false)
|
||||
reset({
|
||||
title: '',
|
||||
username: '',
|
||||
password: '',
|
||||
domainId: domain?.data?.domain?.id,
|
||||
quotaMB: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const getRowActions = (address: AddressData): RowActionItem[] => [
|
||||
{
|
||||
label: 'ویرایش',
|
||||
icon: <Edit2 color='black' size={16} />,
|
||||
onClick: () => handleEdit(address),
|
||||
},
|
||||
{
|
||||
label: 'حذف',
|
||||
icon: <Trash color='red' size={16} />,
|
||||
onClick: () => handleDelete(address),
|
||||
className: 'text-red-600 hover:bg-red-50'
|
||||
}
|
||||
]
|
||||
|
||||
const columns: ColumnType<AddressData>[] = [
|
||||
{
|
||||
title: t('setting.address_title'),
|
||||
@@ -64,7 +169,6 @@ const Address: FC = () => {
|
||||
key: 'quota',
|
||||
width: '300px',
|
||||
render: (item: AddressData) => {
|
||||
|
||||
return (
|
||||
<ProgressBarEmail key={item.id} item={item} />
|
||||
)
|
||||
@@ -80,6 +184,18 @@ const Address: FC = () => {
|
||||
<ToggleStatus defaultStatus={item.isActive} id={item.id} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'عملیات',
|
||||
key: 'actions',
|
||||
width: '120px',
|
||||
align: 'center',
|
||||
render: (item: AddressData) => (
|
||||
<RowActionsDropdown
|
||||
key={item.id}
|
||||
actions={getRowActions(item)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
@@ -89,24 +205,52 @@ const Address: FC = () => {
|
||||
{ value: 'inactive', label: t('setting.inactive') }
|
||||
]
|
||||
|
||||
const onSubmit = (params: CreateAddressType) => {
|
||||
createAddress(params, {
|
||||
onSuccess: (data) => {
|
||||
toast(data?.data?.message, 'success')
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
const onSubmit = (params: FormData) => {
|
||||
if (!validateForm(params)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isEditMode && selectedAddress) {
|
||||
const updateParams: UpdateAddressType = {
|
||||
title: params.title,
|
||||
password: params.password || undefined,
|
||||
quota: params.quotaMB ? params.quotaMB * 1024 * 1024 : undefined,
|
||||
}
|
||||
})
|
||||
updateAddress({ id: selectedAddress.id, params: updateParams }, {
|
||||
onSuccess: (data) => {
|
||||
toast(data?.data?.message || 'آپدیت با موفقیت انجام شد', 'success')
|
||||
handleCancelEdit()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0] || 'خطا در آپدیت', 'error')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const createParams: CreateAddressType = {
|
||||
...params,
|
||||
quota: params.quotaMB ? params.quotaMB * 1024 * 1024 : undefined,
|
||||
}
|
||||
createAddress(createParams, {
|
||||
onSuccess: (data) => {
|
||||
toast(data?.data?.message, 'success')
|
||||
reset()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error?.message[0], 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (domain?.data?.domain?.id) {
|
||||
if (domain?.data?.domain?.id && !isEditMode) {
|
||||
setValue('domainId', domain?.data?.domain?.id)
|
||||
}
|
||||
}, [domain])
|
||||
|
||||
}, [domain, isEditMode])
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1)
|
||||
}, [search, status])
|
||||
|
||||
return (
|
||||
<div className='flex xl:flex-row flex-col-reverse gap-8 mt-8'>
|
||||
@@ -136,26 +280,28 @@ const Address: FC = () => {
|
||||
columns={columns}
|
||||
data={addresses?.data?.users}
|
||||
pagination={{
|
||||
currentPage: 1,
|
||||
totalPages: 6,
|
||||
onPageChange: (page) => console.log('Page:', page)
|
||||
currentPage: currentPage,
|
||||
totalPages: addresses?.data?.pager?.totalPages || 1,
|
||||
onPageChange: (page) => setCurrentPage(page)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='xl:w-[350px] w-full bg-white rounded-4xl p-8'>
|
||||
<div>
|
||||
{t('setting.add_address')}
|
||||
</div>
|
||||
|
||||
{/* <div className='mt-8 flex items-center justify-between'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('setting.status')}
|
||||
{isEditMode ? 'ویرایش آدرس' : t('setting.add_address')}
|
||||
</div>
|
||||
<div className='dltr pt-2'>
|
||||
<Switch />
|
||||
</div>
|
||||
</div> */}
|
||||
{isEditMode && (
|
||||
<Button
|
||||
variant='secondary'
|
||||
onClick={handleCancelEdit}
|
||||
className='px-4 w-fit text-xs'
|
||||
>
|
||||
انصراف
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
@@ -169,7 +315,7 @@ const Address: FC = () => {
|
||||
<Input
|
||||
label={t('setting.title1')}
|
||||
{...register('title')}
|
||||
error_text={errors.title?.message}
|
||||
error_text={errors.title?.message as string}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -178,7 +324,8 @@ const Address: FC = () => {
|
||||
label={t('setting.username')}
|
||||
className='text-left'
|
||||
{...register('username')}
|
||||
error_text={errors.username?.message}
|
||||
error_text={errors.username?.message as string}
|
||||
readOnly={isEditMode}
|
||||
/>
|
||||
<div className='absolute flex items-center dltr text-xs z-10 right-[1px] top-[29px] rounded-r-2.5 h-[38px] bg-[#EBEEF5] px-3'>
|
||||
@ {domain?.data?.domain?.name}
|
||||
@@ -187,38 +334,65 @@ const Address: FC = () => {
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('setting.password')}
|
||||
label='حجم ایمیل (مگابایت)'
|
||||
type='number'
|
||||
{...register('quotaMB', { valueAsNumber: true })}
|
||||
error_text={errors.quotaMB?.message as string}
|
||||
placeholder='حجم ایمیل را به مگابایت وارد کنید'
|
||||
/>
|
||||
<div className='text-xs text-gray-500 mt-1'>
|
||||
فضای باقیمانده: {spaceData?.data?.quota ? Math.round((spaceData.data.quota.totalInGB - spaceData.data.quota.usedInGB) * 1024) : 0} مگابایت
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={isEditMode ? 'رمز عبور جدید' : t('setting.password')}
|
||||
type='password'
|
||||
{...register('password')}
|
||||
error_text={errors.password?.message}
|
||||
error_text={errors.password?.message as string}
|
||||
placeholder={isEditMode ? 'برای تغییر رمز عبور وارد کنید' : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-2 text-xs'>
|
||||
<div>رمز عبور میبایست:</div>
|
||||
<ul className='list-disc pr-3 mt-1 flex flex-col gap-1'>
|
||||
<li>حداقل ۸ کاراکتر باشد</li>
|
||||
<li>ترکیبی از حروف کوچک و بزرگ باشد</li>
|
||||
<li>شامل اعداد باشد</li>
|
||||
<li>شامل کاراکتر های خاص (نماد ها) باشد</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
{!isEditMode && (
|
||||
<div className='mt-2 text-xs'>
|
||||
<div>رمز عبور میبایست:</div>
|
||||
<ul className='list-disc pr-3 mt-1 flex flex-col gap-1'>
|
||||
<li>حداقل ۸ کاراکتر باشد</li>
|
||||
<li>ترکیبی از حروف کوچک و بزرگ باشد</li>
|
||||
<li>شامل اعداد باشد</li>
|
||||
<li>شامل کاراکتر های خاص (نماد ها) باشد</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mt-9 flex justify-end'>
|
||||
<Button
|
||||
className='w-fit px-20'
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isPending}
|
||||
loading={isPending || isUpdating}
|
||||
>
|
||||
<div className='flex gap-2'>
|
||||
<TickCircle size={20} color='white' />
|
||||
<div>
|
||||
{t('setting.create')}
|
||||
{isEditMode ? 'آپدیت' : t('setting.create')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalConfrim
|
||||
isOpen={isDeleteModalOpen}
|
||||
close={handleCloseDeleteModal}
|
||||
onConfrim={handleConfirmDelete}
|
||||
title={t('confrim.delete_address_title')}
|
||||
label={addressToDelete ? t('confrim.delete_address_message').replace('{email}', addressToDelete.emailAddress) : ''}
|
||||
isLoading={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/Service";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { UpdateAddressType } from "../types/Types";
|
||||
|
||||
export const useGetAddress = (search: string, status: string) => {
|
||||
export const useGetAddress = (
|
||||
search: string,
|
||||
status: string,
|
||||
page: number = 1,
|
||||
limit: number = 10
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ["address", search, status],
|
||||
queryFn: () => api.getAddress(search, status),
|
||||
queryKey: ["address", search, status, page, limit],
|
||||
queryFn: () => api.getAddress(search, status, page, limit),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -20,7 +26,32 @@ export const useCreateAddress = () => {
|
||||
};
|
||||
|
||||
export const useToggleStatus = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.toggleStatus,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["address"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateAddress = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, params }: { id: string; params: UpdateAddressType }) =>
|
||||
api.updateAddress(id, params),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["address"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteAddress = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.deleteAddress,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["address"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import axios from "@/config/axios";
|
||||
import { CreateAddressType } from "../types/Types";
|
||||
import { CreateAddressType, UpdateAddressType } from "../types/Types";
|
||||
|
||||
export const getAddress = async (search: string, status: string) => {
|
||||
export const getAddress = async (
|
||||
search: string,
|
||||
status: string,
|
||||
page: number = 1,
|
||||
limit: number = 10
|
||||
) => {
|
||||
const query = new URLSearchParams();
|
||||
if (search) query.set("q", search);
|
||||
if (status && status !== "all")
|
||||
query.set("isActive", status === "active" ? "1" : "0");
|
||||
query.set("page", page.toString());
|
||||
query.set("limit", limit.toString());
|
||||
const { data } = await axios.get(`/users?${query.toString()}`);
|
||||
return data;
|
||||
};
|
||||
@@ -19,3 +26,13 @@ export const toggleStatus = async (id: string) => {
|
||||
const { data } = await axios.patch(`/users/${id}/toggle-status`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateAddress = async (id: string, params: UpdateAddressType) => {
|
||||
const { data } = await axios.patch(`/users/${id}`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteAddress = async (id: string) => {
|
||||
const { data } = await axios.delete(`/users/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,15 @@ export type CreateAddressType = {
|
||||
password: string;
|
||||
domainId: string;
|
||||
title: string;
|
||||
quota?: number;
|
||||
};
|
||||
|
||||
export type UpdateAddressType = {
|
||||
password?: string;
|
||||
displayName?: string;
|
||||
quota?: number;
|
||||
aliases?: string[];
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export interface AddressData extends Record<string, unknown> {
|
||||
@@ -56,3 +65,15 @@ export interface AddressData extends Record<string, unknown> {
|
||||
business: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddressResponse {
|
||||
data: {
|
||||
users: AddressData[];
|
||||
pager: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
import Input from '@/components/Input'
|
||||
import RadioGroup from '@/components/RadioGroup'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMailServerData } from './hooks/useMailServerData'
|
||||
|
||||
const Domain: FC = () => {
|
||||
const MailServer: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8 mt-9'>
|
||||
<div>
|
||||
<div className='flex xl:flex-row gap-4 flex-col justify-between xl:items-end border-b pb-10 broder-border'>
|
||||
<div className='flex-1'>
|
||||
<div className='xl:text-lg'>
|
||||
{t('setting.setting_fetch')}
|
||||
</div>
|
||||
<div className='xl:text-sm text-xs text-description mt-1.5'>
|
||||
{t('setting.setting_fetch_description')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<RadioGroup
|
||||
items={[
|
||||
{
|
||||
label: '۱ دقیقه',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: '۲ دقیقه',
|
||||
value: '2'
|
||||
},
|
||||
{
|
||||
label: '۳ دقیقه',
|
||||
value: '3'
|
||||
}
|
||||
]}
|
||||
onChange={() => null}
|
||||
selected='1'
|
||||
/>
|
||||
</div>
|
||||
const { data, isLoading, error } = useMailServerData();
|
||||
|
||||
console.log('Mail Server Data:', data); // برای debug
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8 mt-9'>
|
||||
<div className='text-center py-8'>
|
||||
در حال بارگذاری...
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8 mt-9'>
|
||||
<div className='text-center py-8 text-red-500'>
|
||||
خطا در بارگذاری اطلاعات
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8 mt-9'>
|
||||
{/* SMTP Settings */}
|
||||
<div className='mt-9'>
|
||||
<div className='flex xl:flex-row gap-4 flex-col justify-between pb-10'>
|
||||
<div className='flex xl:flex-row gap-4 flex-col justify-between pb-10'>
|
||||
<div className='flex-1'>
|
||||
<div className='xl:text-lg'>
|
||||
{t('setting.setting_smtp')}
|
||||
@@ -53,20 +46,57 @@ const Domain: FC = () => {
|
||||
<div className='flex-1'>
|
||||
<Input
|
||||
label={t('setting.smtp_server')}
|
||||
value={data?.data?.setupInfo?.smtpSettings?.server || ''}
|
||||
readOnly
|
||||
/>
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('setting.smtp_port')}
|
||||
value={data?.data?.setupInfo?.smtpSettings?.port?.toString() || ''}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('setting.username')}
|
||||
label={t('setting.encryption')}
|
||||
value={data?.data?.setupInfo?.smtpSettings?.encryption || ''}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* IMAP Settings */}
|
||||
<div className='mt-9 border-t pt-9'>
|
||||
<div className='flex xl:flex-row gap-4 flex-col justify-between pb-10'>
|
||||
<div className='flex-1'>
|
||||
<div className='xl:text-lg'>
|
||||
{t('setting.setting_imap')}
|
||||
</div>
|
||||
<div className='xl:text-sm text-xs text-description mt-1.5'>
|
||||
{t('setting.setting_imap_description')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-1'>
|
||||
<Input
|
||||
label={t('setting.imap_server')}
|
||||
value={data?.data?.setupInfo?.imapSettings?.server || ''}
|
||||
readOnly
|
||||
/>
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('setting.imap_port')}
|
||||
value={data?.data?.setupInfo?.imapSettings?.port?.toString() || ''}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-8'>
|
||||
<Input
|
||||
label={t('setting.password')}
|
||||
label={t('setting.encryption')}
|
||||
value={data?.data?.setupInfo?.imapSettings?.encryption || ''}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,4 +106,4 @@ const Domain: FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default Domain
|
||||
export default MailServer
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getServerInfo } from "../service/Service";
|
||||
|
||||
export const useMailServerData = () => {
|
||||
return useQuery({
|
||||
queryKey: ["mail-server-data"],
|
||||
queryFn: getServerInfo,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import axios from "@/config/axios";
|
||||
|
||||
export const getServerInfo = async () => {
|
||||
const { data } = await axios.get("/mail-server/info");
|
||||
return data;
|
||||
};
|
||||
Reference in New Issue
Block a user