address
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import axiosDanak from "@/config/axiosDanak";
|
||||
import { RefreshTokenType } from "../types/Types";
|
||||
|
||||
export const refreshToken = async (params: RefreshTokenType) => {
|
||||
const { data } = await axiosDanak.post(`/auth/refresh`, params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export type RefreshTokenType = {
|
||||
refreshToken: string;
|
||||
};
|
||||
@@ -1,54 +1,47 @@
|
||||
import Input from '@/components/Input'
|
||||
import Select from '@/components/Select'
|
||||
import Table from '@/components/Table'
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { ColumnType } from '@/components/types/TableTypes'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { AddressData, CreateAddressType } 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 { toast } from '@/components/Toast'
|
||||
import { ErrorType } from '@/helpers/types'
|
||||
import ProgressBarEmail from './components/ProgressBarEmail'
|
||||
import ToggleStatus from './components/ToggleStatus'
|
||||
|
||||
|
||||
interface AddressData extends Record<string, unknown> {
|
||||
id: number
|
||||
title: string
|
||||
email: string
|
||||
status: boolean
|
||||
}
|
||||
|
||||
const Address: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [addresses] = useState<AddressData[]>([
|
||||
{
|
||||
id: 1,
|
||||
title: 'بازاریابی',
|
||||
email: 'Marketing@example.com',
|
||||
status: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'بازاریابی',
|
||||
email: 'Marketing@example.com',
|
||||
status: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'بازاریابی',
|
||||
email: 'Marketing@example.com',
|
||||
status: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'بازاریابی',
|
||||
email: 'Marketing@example.com',
|
||||
status: true
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'بازاریابی',
|
||||
email: 'Marketing@example.com',
|
||||
status: true
|
||||
|
||||
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 { data: domain } = useGetDomains()
|
||||
const { mutate: createAddress, isPending } = useCreateAddress()
|
||||
const { data: addresses, isPending: isPendingAddress } = useGetAddress(search, status)
|
||||
const { register, handleSubmit, formState: { errors },
|
||||
} = useForm<CreateAddressType>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
domainId: domain?.data?.domain?.id || '',
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
const columns: ColumnType<AddressData>[] = [
|
||||
{
|
||||
@@ -59,7 +52,23 @@ const Address: FC = () => {
|
||||
{
|
||||
title: t('setting.email'),
|
||||
key: 'email',
|
||||
width: '300px'
|
||||
width: '300px',
|
||||
render: (item: AddressData) => (
|
||||
<div key={item.id} className='dltr flex justify-end'>
|
||||
{item.emailAddress}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('setting.quota'),
|
||||
key: 'quota',
|
||||
width: '300px',
|
||||
render: (item: AddressData) => {
|
||||
|
||||
return (
|
||||
<ProgressBarEmail key={item.id} item={item} />
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('setting.status'),
|
||||
@@ -68,7 +77,7 @@ const Address: FC = () => {
|
||||
align: 'center',
|
||||
render: (item: AddressData) => (
|
||||
<div key={item.id} className='dltr flex justify-end'>
|
||||
<Switch />
|
||||
<ToggleStatus defaultStatus={item.isActive} id={item.id} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -80,6 +89,17 @@ 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')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex xl:flex-row flex-col-reverse gap-8 mt-8'>
|
||||
<div className='flex-1'>
|
||||
@@ -89,6 +109,7 @@ const Address: FC = () => {
|
||||
placeholder={t('setting.status')}
|
||||
items={statusOptions}
|
||||
className='w-[120px]'
|
||||
onChange={(e) => setStatus(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -97,13 +118,15 @@ const Address: FC = () => {
|
||||
variant='search'
|
||||
placeholder={t('setting.search')}
|
||||
className='xl:w-[300px] flex-1'
|
||||
onChangeSearchFinal={(e) => setSearch(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
isLoading={isPendingAddress}
|
||||
columns={columns}
|
||||
data={addresses}
|
||||
data={addresses?.data?.users}
|
||||
pagination={{
|
||||
currentPage: 1,
|
||||
totalPages: 6,
|
||||
@@ -117,26 +140,28 @@ const Address: FC = () => {
|
||||
{t('setting.add_address')}
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex items-center justify-between'>
|
||||
{/* <div className='mt-8 flex items-center justify-between'>
|
||||
<div>
|
||||
{t('setting.status')}
|
||||
</div>
|
||||
<div className='dltr pt-2'>
|
||||
<Switch />
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('setting.domain1')}
|
||||
value={'example.com'}
|
||||
value={domain?.data?.domain?.name}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label={t('setting.title')}
|
||||
label={t('setting.title1')}
|
||||
{...register('title')}
|
||||
error_text={errors.title?.message}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -144,9 +169,11 @@ const Address: FC = () => {
|
||||
<Input
|
||||
label={t('setting.username')}
|
||||
className='text-left'
|
||||
{...register('username')}
|
||||
error_text={errors.username?.message}
|
||||
/>
|
||||
<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'>
|
||||
@ example.com
|
||||
@ {domain?.data?.domain?.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -154,6 +181,8 @@ const Address: FC = () => {
|
||||
<Input
|
||||
label={t('setting.password')}
|
||||
type='password'
|
||||
{...register('password')}
|
||||
error_text={errors.password?.message}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -170,6 +199,8 @@ const Address: FC = () => {
|
||||
<div className='mt-9 flex justify-end'>
|
||||
<Button
|
||||
className='w-fit px-20'
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isPending}
|
||||
>
|
||||
<div className='flex gap-2'>
|
||||
<TickCircle size={20} color='white' />
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { convertToGB } from '@/config/func'
|
||||
import { FC } from 'react'
|
||||
import { AddressData } from '../types/Types'
|
||||
|
||||
const ProgressBarEmail: FC<{ item: AddressData }> = ({ item }) => {
|
||||
const usedQuota = item.emailQuotaUsed || 0;
|
||||
const totalQuota = item.emailQuota || 0;
|
||||
const percentage = Math.min((usedQuota / totalQuota) * 100, 100);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex justify-between text-sm'>
|
||||
<span className='text-gray-600 dltr'>
|
||||
{convertToGB(usedQuota)} GB / {convertToGB(totalQuota)} GB
|
||||
</span>
|
||||
{/* <span className='text-gray-500'>
|
||||
{percentage.toFixed(1)}%
|
||||
</span> */}
|
||||
</div>
|
||||
<div className='w-full bg-gray-200 dltr overflow-hidden rounded-full h-2'>
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-300 ${percentage > 90 ? 'bg-red-500' :
|
||||
percentage > 70 ? 'bg-yellow-500' :
|
||||
'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProgressBarEmail
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { FC, useState } from 'react'
|
||||
import { useToggleStatus } from '../hooks/useAddressData'
|
||||
|
||||
const ToggleStatus: FC<{ defaultStatus: boolean, id: string }> = ({ defaultStatus, id }) => {
|
||||
const [isActive, setIsActive] = useState(defaultStatus)
|
||||
const { mutate: toggleStatus } = useToggleStatus()
|
||||
|
||||
const handleToggleStatus = () => {
|
||||
toggleStatus(id)
|
||||
setIsActive(!isActive)
|
||||
}
|
||||
return (
|
||||
<Switch checked={isActive} onCheckedChange={handleToggleStatus} />
|
||||
)
|
||||
}
|
||||
|
||||
export default ToggleStatus
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/Service";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const useGetAddress = (search: string, status: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["address", search, status],
|
||||
queryFn: () => api.getAddress(search, status),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateAddress = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.createAddress,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["address"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useToggleStatus = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.toggleStatus,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import axios from "@/config/axios";
|
||||
import { CreateAddressType } from "../types/Types";
|
||||
|
||||
export const getAddress = async (search: string, status: string) => {
|
||||
const query = new URLSearchParams();
|
||||
if (search) query.set("q", search);
|
||||
if (status && status !== "all")
|
||||
query.set("isActive", status === "active" ? "1" : "0");
|
||||
const { data } = await axios.get(`/users?${query.toString()}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createAddress = async (params: CreateAddressType) => {
|
||||
const { data } = await axios.post(`/users`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const toggleStatus = async (id: string) => {
|
||||
const { data } = await axios.patch(`/users/${id}/toggle-status`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
export type CreateAddressType = {
|
||||
username: string;
|
||||
password: string;
|
||||
domainId: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export interface AddressData extends Record<string, unknown> {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
title: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
emailAddress: string;
|
||||
emailEnabled: boolean;
|
||||
emailQuota: number;
|
||||
emailQuotaUsed: number;
|
||||
emailAccountId: string;
|
||||
displayName: string;
|
||||
isActive: boolean;
|
||||
emailSignature: string | null;
|
||||
role: string;
|
||||
business: {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
danakSubscriptionId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
logoUrl: string | null;
|
||||
domain: string;
|
||||
};
|
||||
domain: {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
status: string;
|
||||
isVerified: boolean;
|
||||
isActive: boolean;
|
||||
verifiedAt: string;
|
||||
lastCheckedAt: string | null;
|
||||
notes: string;
|
||||
dkimEnabled: boolean;
|
||||
dkimSelector: string;
|
||||
dkimPrivateKey: string;
|
||||
dkimPublicKey: string;
|
||||
spfEnabled: boolean;
|
||||
spfRecord: string | null;
|
||||
dmarcEnabled: boolean;
|
||||
dmarcPolicy: string | null;
|
||||
business: string;
|
||||
};
|
||||
}
|
||||
@@ -19,11 +19,11 @@ const Signture: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-1 mt-5 xl:mt-0'>
|
||||
<div className='flex-1 mt-4 xl:mt-0'>
|
||||
<UploadBox
|
||||
label={t('setting.upload_sign')}
|
||||
/>
|
||||
<div className='xl:mt-8'>
|
||||
<div className='xl:mt-8 mt-5'>
|
||||
<Textarea
|
||||
label={t('setting.description_sign')}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user