send , receive , detail email
This commit is contained in:
+24
-3
@@ -1,4 +1,4 @@
|
||||
import { FC } from 'react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import './assets/fonts/irancell/style.css'
|
||||
import 'react-quill-new/dist/quill.snow.css';
|
||||
import 'react-loading-skeleton/dist/skeleton.css'
|
||||
@@ -12,8 +12,10 @@ import NewMessage from './components/newMessage/NewMessage';
|
||||
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { IApiErrorRepsonse } from './types/error.types';
|
||||
import ToastContainer from './components/Toast';
|
||||
import { getRefreshToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func';
|
||||
import { getRefreshToken, getToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func';
|
||||
import { refreshToken } from './pages/auth/service/AuthService';
|
||||
import { Paths } from './utils/Paths';
|
||||
import Login from './pages/auth/Login';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -96,12 +98,31 @@ const queryClient = new QueryClient({
|
||||
});
|
||||
|
||||
const App: FC = () => {
|
||||
|
||||
const [isLogin, setIsLogin] = useState<'checking' | 'isLogin' | 'isNotLogin'>('checking')
|
||||
useEffect(() => {
|
||||
const checkLogin = async () => {
|
||||
const token = await getToken();
|
||||
if (token) {
|
||||
setIsLogin('isLogin')
|
||||
} else {
|
||||
setIsLogin('isNotLogin')
|
||||
if (window.location.href.split('auth').length === 1) {
|
||||
window.location.href = Paths.login
|
||||
}
|
||||
}
|
||||
}
|
||||
checkLogin();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
||||
<Main />
|
||||
{
|
||||
isLogin === 'isLogin' ? <Main /> : isLogin === 'isNotLogin' ? <Login /> : null
|
||||
}
|
||||
<NewMessage />
|
||||
<ToastContainer />
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -36,7 +36,7 @@ const ToastContainer: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="fixed top-4 text-sm right-0 left-0 mx-auto w-fit z-50 flex flex-col gap-2">
|
||||
<div className="fixed top-4 text-sm right-0 left-0 mx-auto w-fit z-[99999999999] flex flex-col gap-2">
|
||||
{toasts.map((toast) => (
|
||||
<div
|
||||
key={toast.id}
|
||||
|
||||
@@ -2,21 +2,37 @@ import { FC, useState, useEffect } from 'react'
|
||||
import { CloseCircle } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSharedStore } from '@/shared/store/sharedStore'
|
||||
import { useAuthStore } from '../../pages/auth/store/AuthStore'
|
||||
import { useSendEmail, useSaveDraft } from '../../pages/received/hooks/useEmailData'
|
||||
import { SendEmailDto } from '../../pages/received/types/Types'
|
||||
import Input from '../Input'
|
||||
import ReactQuill from 'react-quill-new';
|
||||
import Button from '../Button'
|
||||
import Select from '../Select'
|
||||
import UploadButton from '../UploadButton'
|
||||
import { toast } from '../Toast'
|
||||
|
||||
|
||||
const NewMessage: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
const [value, setValue] = useState('');
|
||||
const { setOpenNewMessage, openNewMessage } = useSharedStore()
|
||||
const { email: userEmail } = useAuthStore()
|
||||
const [isClosing, setIsClosing] = useState(false)
|
||||
const [shouldRender, setShouldRender] = useState(false)
|
||||
const [isOpening, setIsOpening] = useState(false)
|
||||
|
||||
// Form state
|
||||
const [to, setTo] = useState('')
|
||||
const [toEmails, setToEmails] = useState<string[]>([])
|
||||
const [subject, setSubject] = useState('')
|
||||
const [priority, setPriority] = useState('')
|
||||
|
||||
|
||||
// API hooks
|
||||
const sendEmailMutation = useSendEmail()
|
||||
const saveDraftMutation = useSaveDraft()
|
||||
|
||||
useEffect(() => {
|
||||
if (openNewMessage) {
|
||||
setShouldRender(true)
|
||||
@@ -42,6 +58,108 @@ const NewMessage: FC = () => {
|
||||
setOpenNewMessage(false)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const parseEmailAddresses = (emails: string[]) => {
|
||||
return emails
|
||||
.filter(email => email.length > 0)
|
||||
.map(email => ({ address: email }))
|
||||
}
|
||||
|
||||
const isValidEmail = (email: string) => {
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
const handleToKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' && to.trim()) {
|
||||
e.preventDefault()
|
||||
const email = to.trim()
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
toast('لطفا یک ایمیل معتبر وارد کنید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
if (toEmails.includes(email)) {
|
||||
toast('این ایمیل قبلاً اضافه شده است', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
setToEmails([...toEmails, email])
|
||||
setTo('')
|
||||
}
|
||||
}
|
||||
|
||||
const removeEmail = (emailToRemove: string) => {
|
||||
setToEmails(toEmails.filter(email => email !== emailToRemove))
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleSend = async () => {
|
||||
if (toEmails.length === 0 || !subject) {
|
||||
toast('لطفا تمام فیلدهای مورد نیاز را پر کنید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
const emailData: SendEmailDto = {
|
||||
from: { address: userEmail || 'sender@example.com' },
|
||||
to: parseEmailAddresses(toEmails),
|
||||
subject,
|
||||
html: value,
|
||||
text: value.replace(/<[^>]*>/g, ''), // Strip HTML for text version
|
||||
}
|
||||
|
||||
try {
|
||||
await sendEmailMutation.mutateAsync(emailData)
|
||||
toast('ایمیل با موفقیت ارسال شد', 'success')
|
||||
handleClose()
|
||||
// Reset form
|
||||
setTo('')
|
||||
setToEmails([])
|
||||
setSubject('')
|
||||
setValue('')
|
||||
setPriority('')
|
||||
} catch {
|
||||
toast('خطا در ارسال ایمیل', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveDraft = async () => {
|
||||
if (toEmails.length === 0 && !subject && !value) {
|
||||
toast('حداقل یکی از فیلدها باید پر باشد', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
const emailData: SendEmailDto = {
|
||||
from: { address: userEmail || 'sender@example.com' },
|
||||
to: parseEmailAddresses(toEmails),
|
||||
subject,
|
||||
html: value,
|
||||
text: value.replace(/<[^>]*>/g, ''),
|
||||
isDraft: true,
|
||||
}
|
||||
|
||||
try {
|
||||
await saveDraftMutation.mutateAsync(emailData)
|
||||
toast('پیشنویس ذخیره شد', 'success')
|
||||
handleClose()
|
||||
// Reset form
|
||||
setTo('')
|
||||
setToEmails([])
|
||||
setSubject('')
|
||||
setValue('')
|
||||
setPriority('')
|
||||
} catch {
|
||||
toast('خطا در ذخیره پیشنویس', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const priorityOptions = [
|
||||
{ value: 'high', label: 'بالا' },
|
||||
{ value: 'medium', label: 'متوسط' },
|
||||
{ value: 'low', label: 'پایین' },
|
||||
]
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -66,12 +184,44 @@ const NewMessage: FC = () => {
|
||||
|
||||
{/* Content */}
|
||||
<div className='space-y-6 md:space-y-8'>
|
||||
<Input
|
||||
label={t('new_message.to')}
|
||||
/>
|
||||
<div>
|
||||
<label className='text-sm'>
|
||||
{t('new_message.to')}
|
||||
</label>
|
||||
<div className='w-full relative mt-1'>
|
||||
<div className="w-full bg-white min-h-10 text-black flex flex-wrap items-center gap-1 px-4 py-2 text-xs rounded-xl border border-border">
|
||||
{/* Email Tags */}
|
||||
{toEmails.map((email, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-1 bg-[#EBEDF5] text-xs h-7 rounded-full px-3"
|
||||
>
|
||||
<span>{email}</span>
|
||||
<CloseCircle
|
||||
size={16}
|
||||
color='#D52903'
|
||||
className='cursor-pointer'
|
||||
onClick={() => removeEmail(email)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Input Field */}
|
||||
<input
|
||||
value={to}
|
||||
onChange={(e) => setTo(e.target.value)}
|
||||
onKeyDown={handleToKeyDown}
|
||||
placeholder={toEmails.length === 0 ? "ایمیل را وارد کرده و Enter بزنید" : ""}
|
||||
className="flex-1 min-w-32 bg-transparent outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label={t('new_message.subject')}
|
||||
value={subject}
|
||||
onChange={(e) => setSubject(e.target.value)}
|
||||
/>
|
||||
|
||||
<div>
|
||||
@@ -101,19 +251,25 @@ const NewMessage: FC = () => {
|
||||
<div className='flex flex-col gap-4 sm:flex-row pt-4'>
|
||||
<div>
|
||||
<Select
|
||||
items={[]}
|
||||
items={priorityOptions}
|
||||
placeholder={t('new_message.select_priority')}
|
||||
className='xl:w-[190px] min-w-[130px]'
|
||||
value={priority}
|
||||
onChange={(e) => setPriority(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-1 justify-end flex gap-3'>
|
||||
<Button
|
||||
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
|
||||
label={t('new_message.draft')}
|
||||
onClick={handleSaveDraft}
|
||||
loading={saveDraftMutation.isPending}
|
||||
/>
|
||||
<Button
|
||||
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
|
||||
label={t('new_message.send')}
|
||||
onClick={handleSend}
|
||||
loading={sendEmailMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,6 +277,8 @@ const NewMessage: FC = () => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,3 +69,14 @@ export const getRedirectUrl = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get("redirect");
|
||||
};
|
||||
|
||||
export const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return new Intl.DateTimeFormat("fa-IR", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ textarea::placeholder {
|
||||
min-height: 40px;
|
||||
background-color: white;
|
||||
border-radius: 12px !important;
|
||||
border: 1px solid #d0d0d0 !important;
|
||||
border: 1px solid oklch(0.922 0 0) !important;
|
||||
font-size: 12px !important;
|
||||
width: 100% !important;
|
||||
margin: 0px;
|
||||
|
||||
+47
-1
@@ -18,7 +18,8 @@
|
||||
"search": "جستجو"
|
||||
},
|
||||
"errors": {
|
||||
"required": "این فیلد اجباری است"
|
||||
"required": "این فیلد اجباری است",
|
||||
"invalid_email": "فرمت ایمیل نامعتبر است"
|
||||
},
|
||||
"received": {
|
||||
"title": "دریافتی ها",
|
||||
@@ -161,5 +162,50 @@
|
||||
"answer": "پاسخ",
|
||||
"forward": "ارسال به دیگران",
|
||||
"message": "متن پبام"
|
||||
},
|
||||
"auth": {
|
||||
"welcome": "خوش آمدید",
|
||||
"enter_information": "برای ثبت نام اطلاعات خود را وارد کنید.",
|
||||
"user_info": "اطلاعات کاربر",
|
||||
"name": "نام",
|
||||
"enter_name": "نام خود را وارد کنید",
|
||||
"family": "نام خانوادگی",
|
||||
"enter_family": "نام خانوادگی خود را وارد کنید",
|
||||
"select_date": "انتخاب تاریخ",
|
||||
"dateofbirth": "تاریخ تولد",
|
||||
"national_code": "کد ملی",
|
||||
"national_code_enter": "کد ملی خود را وارد کنید",
|
||||
"email": "ایمیل",
|
||||
"enter_email": "ایمیل خود را وارد کنید",
|
||||
"phonen_number": "شماره تماس",
|
||||
"enter_phone_number": "شماره تماس خود را وارد کنید",
|
||||
"password": "رمز عبور",
|
||||
"enter_password": "رمز عبور انتخابی خود را وارد کنید",
|
||||
"next": "ادامه",
|
||||
"before_registered": "آیا قبلا ثبت نام کردید؟",
|
||||
"login": "ورود",
|
||||
"enter_info_login": "لطفا اطلاعات خود را وارد کنید.",
|
||||
"mobile_or_email": " شماره موبایل یا ایمیل",
|
||||
"enter_mobile_or_email": " شماره موبایل یا ایمیل خود را وارد کنید",
|
||||
"have_account": "آیا حساب کاربری ندارید؟",
|
||||
"register": "ثبت نام",
|
||||
"enter_otp": "کد تایید را وارد کنید",
|
||||
"verfify_code_text_1": "کد تایید برای ",
|
||||
"verfify_code_text_2": "ارسال شد.",
|
||||
"change_number": "تغییر شماره",
|
||||
"verify_code": "کد تایید ",
|
||||
"enter_verify_code": "کد تایید ارسالی را وارد کنید ",
|
||||
"counter_otp": "مانده تا دریافت مجدد کد",
|
||||
"login_with_password": "ورود با رمز عبور",
|
||||
"enter_password_step3": "رمز عبور را وارد کنید",
|
||||
"enter_your_password": "رمز عبور خود را وارد کنید.",
|
||||
"forgot_password": "فراموشی رمز عبور",
|
||||
"login_with_otp": "ورود با رمز عبور یکبار مصرف",
|
||||
"otp_sent": "کد تایید ارسال شد",
|
||||
"mobile_number": "شماره موبایل",
|
||||
"enter_mobile_number": "شماره موبایل خود را وارد کنید",
|
||||
"try_again": "ارسال مجدد",
|
||||
"toast_resend_code": "کد جدیدی برای شما ارسال شد",
|
||||
"old_version": "نسخه قدیمی داشبورد"
|
||||
}
|
||||
}
|
||||
|
||||
+99
-22
@@ -1,40 +1,117 @@
|
||||
import { FC } from 'react'
|
||||
import LogoImage from '../../assets/images/logo.svg'
|
||||
import LogoSmallImage from '../../assets/images/logo-small.svg'
|
||||
import { useAuthStore } from './store/AuthStore'
|
||||
import LoginStep1 from './components/LoginStep1'
|
||||
import LoginStep2 from './components/LoginStep2'
|
||||
import LoginStep3 from './components/LoginStep3'
|
||||
import Input from '../../components/Input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import Error from '../../components/Error'
|
||||
import Button from '../../components/Button'
|
||||
import { useLoginWithPassword } from './hooks/useAuthData'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { setRefreshToken, setToken, getRedirectUrl } from '../../config/func'
|
||||
import { toast } from '../../components/Toast'
|
||||
import { Paths } from '../../utils/Paths'
|
||||
|
||||
type LoginFormType = {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const Login: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
const loginWithPassword = useLoginWithPassword()
|
||||
|
||||
const { stepLogin, phone, email } = useAuthStore()
|
||||
|
||||
const formik = useFormik<LoginFormType>({
|
||||
initialValues: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
email: Yup.string()
|
||||
.email(t('errors.invalid_email'))
|
||||
.required(t('errors.required')),
|
||||
password: Yup.string()
|
||||
.required(t('errors.required'))
|
||||
}),
|
||||
onSubmit(values) {
|
||||
loginWithPassword.mutate(values, {
|
||||
onSuccess(data) {
|
||||
setToken(data?.data?.accessToken?.token)
|
||||
setRefreshToken(data?.data?.refreshToken?.token)
|
||||
const redirectUrl = getRedirectUrl();
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl;
|
||||
return;
|
||||
}
|
||||
window.location.href = Paths.received
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast(error?.response?.data?.error?.message[0], 'error')
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='w-full h-full flex justify-center lg:py-[75px] py-4 lg:items-center lg:px-10 px-4'>
|
||||
<div className='flex w-full max-h-[812px] max-w-[1200px] bg-secondary h-full rounded-3xl overflow-hidden'>
|
||||
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
|
||||
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
|
||||
<div className='flex-1 h-full flex flex-col'>
|
||||
|
||||
<div className='flex relative flex-1 flex-col h-full justify-center'>
|
||||
<img src={LogoSmallImage} className='w-8 hidden xl:block' />
|
||||
<img src={LogoImage} className='w-44 right-0 left-0 mx-auto absolute top-10 xl:hidden' />
|
||||
{
|
||||
stepLogin === 1 ?
|
||||
<LoginStep1 />
|
||||
:
|
||||
stepLogin === 2 && !!phone ?
|
||||
<LoginStep2 />
|
||||
: stepLogin === 2 && !!email ?
|
||||
<LoginStep3 />
|
||||
: stepLogin === 3 ?
|
||||
<LoginStep3 />
|
||||
: null
|
||||
}
|
||||
|
||||
<div>
|
||||
<div className='mt-5'>
|
||||
<h2 className='text-2xl font-bold'>
|
||||
{t('auth.welcome')}
|
||||
</h2>
|
||||
<p className='text-description text-sm mt-2'>
|
||||
{t('auth.enter_info_login')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='mt-16 space-y-6'>
|
||||
<div>
|
||||
<Input
|
||||
label={t('auth.email')}
|
||||
placeholder={t('auth.enter_email')}
|
||||
type='email'
|
||||
className='text-right'
|
||||
name='email'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.email}
|
||||
/>
|
||||
{formik.touched.email && formik.errors.email && (
|
||||
<Error errorText={formik.errors.email} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
label={t('auth.password')}
|
||||
placeholder={t('auth.enter_password')}
|
||||
type='password'
|
||||
className='text-right'
|
||||
name='password'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.password}
|
||||
/>
|
||||
{formik.touched.password && formik.errors.password && (
|
||||
<Error errorText={formik.errors.password} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
label={t('auth.login')}
|
||||
className='mt-8'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
loading={loginWithPassword.isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "../types/AuthTypes";
|
||||
|
||||
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
||||
const { data } = await axios.post(`/auth/login/password`, params);
|
||||
const { data } = await axios.post(`/auth/login`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
+145
-14
@@ -1,6 +1,7 @@
|
||||
import Button from '@/components/Button'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import AvatarImage from '@/assets/images/avatar.svg'
|
||||
import { DocumentDownload, EmojiHappy } from 'iconsax-react'
|
||||
import ForwardIcon from '@/assets/images/forward.svg'
|
||||
@@ -8,12 +9,125 @@ import AnswerIcon from '@/assets/images/answer.svg'
|
||||
import Header from './Components/Header'
|
||||
import ReactQuill from 'react-quill-new';
|
||||
import Select from '@/components/Select'
|
||||
import { useGetMessageDetail, useSendEmail, useSaveDraft } from './hooks/useEmailData'
|
||||
import { useAuthStore } from '../auth/store/AuthStore'
|
||||
import { SendEmailDto } from './types/Types'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { formatDate } from '@/config/func'
|
||||
|
||||
const DetailEmail: FC = () => {
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const { email: userEmail } = useAuthStore()
|
||||
const [showAnswer, setShowAnswer] = useState<boolean>(false)
|
||||
const [value, setValue] = useState<string>('')
|
||||
const [priority, setPriority] = useState<string>('')
|
||||
|
||||
// API hooks
|
||||
const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', !!id)
|
||||
const sendEmailMutation = useSendEmail()
|
||||
const saveDraftMutation = useSaveDraft()
|
||||
|
||||
const priorityOptions = [
|
||||
{ value: 'high', label: 'بالا' },
|
||||
{ value: 'medium', label: 'متوسط' },
|
||||
{ value: 'low', label: 'پایین' },
|
||||
]
|
||||
|
||||
const handleSendReply = async () => {
|
||||
if (!value.trim()) {
|
||||
toast('لطفا متن پیام را وارد کنید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
if (!messageDetail) {
|
||||
toast('اطلاعات پیام یافت نشد', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
const replyData: SendEmailDto = {
|
||||
from: { address: userEmail || 'sender@example.com' },
|
||||
to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
|
||||
subject: `Re: ${messageDetail.subject}`,
|
||||
html: value,
|
||||
text: value.replace(/<[^>]*>/g, ''),
|
||||
reference: {
|
||||
inReplyTo: messageDetail.messageId,
|
||||
originalMessageId: messageDetail.id.toString()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await sendEmailMutation.mutateAsync(replyData)
|
||||
toast('پاسخ با موفقیت ارسال شد', 'success')
|
||||
setShowAnswer(false)
|
||||
setValue('')
|
||||
setPriority('')
|
||||
} catch {
|
||||
toast('خطا در ارسال پاسخ', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveDraft = async () => {
|
||||
if (!value.trim()) {
|
||||
toast('متن پیام خالی است', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
if (!messageDetail) {
|
||||
toast('اطلاعات پیام یافت نشد', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
const draftData: SendEmailDto = {
|
||||
from: { address: userEmail || 'sender@example.com' },
|
||||
to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
|
||||
subject: `Re: ${messageDetail.subject}`,
|
||||
html: value,
|
||||
text: value.replace(/<[^>]*>/g, ''),
|
||||
isDraft: true,
|
||||
reference: {
|
||||
inReplyTo: messageDetail.messageId,
|
||||
originalMessageId: messageDetail.id.toString()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await saveDraftMutation.mutateAsync(draftData)
|
||||
toast('پیشنویس ذخیره شد', 'success')
|
||||
} catch {
|
||||
toast('خطا در ذخیره پیشنویس', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const handleForward = () => {
|
||||
// TODO: Implement forward functionality
|
||||
toast('قابلیت ارسال به دیگران به زودی اضافه خواهد شد', 'info')
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<div className='animate-pulse'>
|
||||
<div className='h-6 bg-gray-200 rounded mb-4'></div>
|
||||
<div className='h-4 bg-gray-200 rounded mb-2'></div>
|
||||
<div className='h-4 bg-gray-200 rounded mb-2'></div>
|
||||
<div className='h-20 bg-gray-200 rounded'></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !messageDetail) {
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<div className='text-center text-red-500'>
|
||||
خطا در بارگذاری پیام
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
@@ -23,7 +137,7 @@ const DetailEmail: FC = () => {
|
||||
<div className='flex xl:flex-row flex-col-reverse gap-4 xl:items-center'>
|
||||
<div className='flex gap-1 items-center'>
|
||||
<div className='font-extralight hidden xl:block'>{t('mail.subject')}</div>
|
||||
<div className='leading-7 text-sm'>لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ</div>
|
||||
<div className='leading-7 text-sm'>{messageDetail.subject || 'بدون موضوع'}</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
@@ -38,28 +152,41 @@ const DetailEmail: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className='flex justify-end text-description xl:text-sm text-[10px]'>
|
||||
۵ اسفندماه ۱۴۰۳، ساعت ۱۲:۰۰
|
||||
{formatDate(messageDetail.date)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex gap-4 items-center'>
|
||||
<img src={AvatarImage} className='size-10' />
|
||||
<div className='text-sm'>مهرداد مظفری</div>
|
||||
<div className='text-sm'>{messageDetail.from.name || messageDetail.from.address}</div>
|
||||
</div>
|
||||
|
||||
<p className='xl:mt-8 mt-3 text-[13px] leading-7 font-light'>
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد کتابهای زیادی در شصت و سه درصد گذشته حال و آیندهلورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد کتابهای زیادی در شصت و سه درصد گذشته حال و آینده
|
||||
</p>
|
||||
<div className='xl:mt-8 mt-3 text-[13px] leading-7 font-light'>
|
||||
{messageDetail.html && messageDetail.html.length > 0 ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: messageDetail.html.join('') }} />
|
||||
) : (
|
||||
<p>{messageDetail.text || 'محتوای پیام'}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='mt-10 text-sm font-bold'>
|
||||
{t('mail.attachments')}
|
||||
</div>
|
||||
|
||||
<div className='flex border-b border-border pb-10 mt-4 flex-wrap gap-2.5 items-center'>
|
||||
<div className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
|
||||
<span>Lorem Ipsum.pdf</span>
|
||||
<DocumentDownload size={20} color='#0038FF' />
|
||||
</div>
|
||||
{messageDetail.attachments && messageDetail.attachments.length > 0 ? (
|
||||
messageDetail.attachments.map((attachment, index) => (
|
||||
<div key={index} className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
|
||||
<span>{attachment.filename || `attachment-${index + 1}`}</span>
|
||||
<DocumentDownload size={20} color='#0038FF' className='cursor-pointer' />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
|
||||
<span>Lorem Ipsum.pdf</span>
|
||||
<DocumentDownload size={20} color='#0038FF' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{
|
||||
@@ -73,7 +200,7 @@ const DetailEmail: FC = () => {
|
||||
<div>{t('mail.answer')}</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 cursor-pointer items-center border-r border-border pr-5'>
|
||||
<div onClick={handleForward} className='flex gap-2 cursor-pointer items-center border-r border-border pr-5'>
|
||||
<img src={ForwardIcon} className='w-[17px]' />
|
||||
<div>{t('mail.forward')}</div>
|
||||
</div>
|
||||
@@ -108,19 +235,25 @@ const DetailEmail: FC = () => {
|
||||
<div className='flex flex-col gap-4 sm:flex-row pt-4'>
|
||||
<div>
|
||||
<Select
|
||||
items={[]}
|
||||
items={priorityOptions}
|
||||
placeholder={t('new_message.select_priority')}
|
||||
className='xl:w-[190px]'
|
||||
value={priority}
|
||||
onChange={(e) => setPriority(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-1 justify-end flex gap-3'>
|
||||
<Button
|
||||
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
|
||||
label={t('new_message.draft')}
|
||||
onClick={handleSaveDraft}
|
||||
loading={saveDraftMutation.isPending}
|
||||
/>
|
||||
<Button
|
||||
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
|
||||
label={t('new_message.send')}
|
||||
onClick={handleSendReply}
|
||||
loading={sendEmailMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,8 +261,6 @@ const DetailEmail: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+56
-46
@@ -3,46 +3,43 @@ import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Table from '../../components/Table';
|
||||
import { ColumnType } from '../../components/types/TableTypes';
|
||||
import { InfoCircle, More, Refresh2, Trash, Edit, Archive, ArchiveSlash } from 'iconsax-react';
|
||||
import { InfoCircle, More, Refresh2, Trash, Edit, Archive } from 'iconsax-react';
|
||||
import { RowActionItem } from '../../components/RowActionsDropdown';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface Message extends Record<string, unknown> {
|
||||
id: string;
|
||||
title: string;
|
||||
sender: string;
|
||||
date: string;
|
||||
read: boolean;
|
||||
}
|
||||
import { useGetInbox } from './hooks/useEmailData';
|
||||
import { InboxMessage } from './types/Types';
|
||||
import { formatDate } from '@/config/func';
|
||||
|
||||
const List: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [isLoading] = useState(false);
|
||||
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [filters, setFilters] = useState<FilterValues>({});
|
||||
|
||||
const messageData: Message[] = [
|
||||
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
|
||||
{ id: '2', title: 'دومین پیام', sender: 'زهرا احمدی', date: '1402/05/13', read: true },
|
||||
{ id: '3', title: 'جلسه هفتگی', sender: 'مدیریت', date: '1402/05/14', read: true },
|
||||
];
|
||||
const { data: inboxData, isLoading, refetch } = useGetInbox({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
...filters
|
||||
});
|
||||
|
||||
const columns: ColumnType<Message>[] = [
|
||||
const [selectedMessages, setSelectedMessages] = useState<InboxMessage[]>([]);
|
||||
|
||||
const columns: ColumnType<InboxMessage>[] = [
|
||||
{
|
||||
key: 'title',
|
||||
key: 'subject',
|
||||
title: 'عنوان پیام',
|
||||
render: (item) => (
|
||||
<div className="flex items-center">
|
||||
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
|
||||
<span className="truncate">{item.title}</span>
|
||||
{/* {!item.seen && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />} */}
|
||||
<span className="truncate">{item.subject || 'بدون موضوع'}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'sender',
|
||||
key: 'from',
|
||||
title: 'فرستنده',
|
||||
render: (item) => (
|
||||
<span className="truncate">{item.sender}</span>
|
||||
<span className="truncate">{item.from.name || item.from.address}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -50,14 +47,19 @@ const List: FC = () => {
|
||||
title: 'تاریخ',
|
||||
align: 'center',
|
||||
render: (item) => (
|
||||
<span className="text-xs md:text-sm">{item.date}</span>
|
||||
<span>{formatDate(item.date)}</span>
|
||||
)
|
||||
},
|
||||
];
|
||||
|
||||
const tableActions = (
|
||||
<div className='flex items-center gap-2 md:gap-4'>
|
||||
<Refresh2 size={18} color='black' className="cursor-pointer" />
|
||||
<Refresh2
|
||||
size={18}
|
||||
color='black'
|
||||
className="cursor-pointer"
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
<More size={18} color='black' className='rotate-90 cursor-pointer' />
|
||||
</div>
|
||||
);
|
||||
@@ -71,10 +73,11 @@ const List: FC = () => {
|
||||
);
|
||||
|
||||
const handleFilterChange = (newFilters: FilterValues) => {
|
||||
console.log('Applied filters:', newFilters);
|
||||
setFilters(newFilters);
|
||||
setCurrentPage(1); // Reset to first page when filters change
|
||||
};
|
||||
|
||||
const handleSelectionChange = (selectedRows: Message[]) => {
|
||||
const handleSelectionChange = (selectedRows: InboxMessage[]) => {
|
||||
setSelectedMessages(selectedRows);
|
||||
};
|
||||
|
||||
@@ -93,16 +96,16 @@ const List: FC = () => {
|
||||
// اینجا منطق علامتگذاری را پیادهسازی کنید
|
||||
};
|
||||
|
||||
const getRowActions = (message: Message): RowActionItem[] => [
|
||||
const getRowActions = (message: InboxMessage): RowActionItem[] => [
|
||||
{
|
||||
label: 'خروج از آرشیو',
|
||||
icon: <ArchiveSlash size={16} color="black" />,
|
||||
onClick: () => console.log('خروج از آرشیو', message.id),
|
||||
label: message.seen ? 'علامتگذاری به عنوان خوانده نشده' : 'علامتگذاری به عنوان خوانده شده',
|
||||
icon: <Edit size={16} color="black" />,
|
||||
onClick: () => console.log('تغییر وضعیت خوانده شده', message.id),
|
||||
},
|
||||
{
|
||||
label: 'خوانده شده',
|
||||
icon: <Edit size={16} color="black" />,
|
||||
onClick: () => console.log('علامتگذاری به عنوان خوانده شده', message.id),
|
||||
label: 'بایگانی',
|
||||
icon: <Archive size={16} color="black" />,
|
||||
onClick: () => console.log('بایگانی پیام', message.id),
|
||||
},
|
||||
{
|
||||
label: 'حذف',
|
||||
@@ -111,22 +114,29 @@ const List: FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const messages = inboxData?.data?.results || [];
|
||||
const pager = inboxData?.data?.pager;
|
||||
|
||||
return (
|
||||
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||
<h1 className="text-lg mb-4 md:mb-0">{t('received.title')}</h1>
|
||||
|
||||
<Filters
|
||||
fields={[
|
||||
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
|
||||
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
|
||||
{ type: 'date', name: 'dateFrom', placeholder: t('received.from_date') },
|
||||
{ type: 'date', name: 'dateTo', placeholder: t('received.to_date') },
|
||||
{
|
||||
type: 'select',
|
||||
name: 'status',
|
||||
name: 'isRead',
|
||||
placeholder: t('received.all'),
|
||||
options: [
|
||||
{ value: 'all', label: t('received.all') },
|
||||
{ value: 'read', label: t('received.read') },
|
||||
{ value: 'unread', label: t('received.unread') }
|
||||
{ value: '', label: t('received.all') },
|
||||
{ value: 'true', label: t('received.read') },
|
||||
{ value: 'false', label: t('received.unread') }
|
||||
]
|
||||
},
|
||||
{ type: 'input', name: 'search', placeholder: t('received.search') }
|
||||
@@ -135,9 +145,9 @@ const List: FC = () => {
|
||||
searchField="search"
|
||||
/>
|
||||
|
||||
<Table<Message>
|
||||
<Table<InboxMessage>
|
||||
columns={columns}
|
||||
data={messageData}
|
||||
data={messages}
|
||||
isLoading={isLoading}
|
||||
showHeader={false}
|
||||
actions={tableActions}
|
||||
@@ -152,11 +162,11 @@ const List: FC = () => {
|
||||
}
|
||||
selectable={true}
|
||||
rowActions={getRowActions}
|
||||
pagination={{
|
||||
totalPages: 10,
|
||||
currentPage: 1,
|
||||
onPageChange: () => { }
|
||||
}}
|
||||
pagination={pager ? {
|
||||
totalPages: pager.totalPages,
|
||||
currentPage: pager.page,
|
||||
onPageChange: handlePageChange
|
||||
} : undefined}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import * as api from '../service/EmailService';
|
||||
import { SendEmailDto, MessageListQueryDto } from '../types/Types';
|
||||
|
||||
export const useSendEmail = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: SendEmailDto) => api.sendEmail(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useSaveDraft = () => {
|
||||
return useMutation({
|
||||
mutationFn: (variables: SendEmailDto) => api.saveDraft(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetInbox = (query: MessageListQueryDto) => {
|
||||
return useQuery({
|
||||
queryKey: ['inbox', query],
|
||||
queryFn: () => api.getInbox(query),
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetMessageDetail = (messageId: string, enabled: boolean = true) => {
|
||||
return useQuery({
|
||||
queryKey: ['message', messageId],
|
||||
queryFn: () => api.getMessageDetail(messageId),
|
||||
enabled: !!messageId && enabled,
|
||||
staleTime: 1000 * 60 * 10, // 10 minutes
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteMessage = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (messageId: string) => api.deleteMessage(messageId),
|
||||
onSuccess: () => {
|
||||
// Invalidate inbox queries to refresh the list
|
||||
queryClient.invalidateQueries({ queryKey: ['inbox'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import axios from "../../../config/axios";
|
||||
import {
|
||||
SendEmailDto,
|
||||
MessageListQueryDto,
|
||||
InboxResponse,
|
||||
MessageDetailResponse,
|
||||
MessageDetail,
|
||||
} from "../types/Types";
|
||||
|
||||
export const sendEmail = async (params: SendEmailDto) => {
|
||||
const { data } = await axios.post(`/email/send`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const saveDraft = async (params: SendEmailDto) => {
|
||||
const { data } = await axios.post(`/email/draft`, {
|
||||
...params,
|
||||
isDraft: true,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getInbox = async (
|
||||
query: MessageListQueryDto
|
||||
): Promise<InboxResponse> => {
|
||||
const { data } = await axios.get(`/email/messages/inbox`, { params: query });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getMessageDetail = async (
|
||||
messageId: string
|
||||
): Promise<MessageDetail> => {
|
||||
const { data }: { data: MessageDetailResponse } = await axios.get(
|
||||
`/email/messages/inbox/${messageId}`
|
||||
);
|
||||
return data.data.message;
|
||||
};
|
||||
|
||||
export const deleteMessage = async (messageId: string): Promise<void> => {
|
||||
const { data } = await axios.delete(`/email/messages/inbox/${messageId}`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,288 @@
|
||||
export interface EmailRecipientDto {
|
||||
name?: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface EmailHeaderDto {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface EmailAttachmentDto {
|
||||
filename?: string;
|
||||
contentType?: string;
|
||||
encoding?: string;
|
||||
contentTransferEncoding?: string;
|
||||
contentDisposition?: "inline" | "attachment";
|
||||
content: string;
|
||||
cid?: string;
|
||||
}
|
||||
|
||||
export interface DraftReferenceDto {
|
||||
mailbox: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface EmailEnvelopeDto {
|
||||
from: EmailRecipientDto;
|
||||
to: EmailRecipientDto[];
|
||||
}
|
||||
|
||||
export interface SendEmailDto {
|
||||
mailbox?: string;
|
||||
from: EmailRecipientDto;
|
||||
replyTo?: EmailRecipientDto;
|
||||
to: EmailRecipientDto[];
|
||||
cc?: EmailRecipientDto[];
|
||||
bcc?: EmailRecipientDto[];
|
||||
headers?: EmailHeaderDto[];
|
||||
subject?: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
attachments?: EmailAttachmentDto[];
|
||||
meta?: Record<string, unknown>;
|
||||
sess?: string;
|
||||
ip?: string;
|
||||
reference?: Record<string, unknown>;
|
||||
isDraft?: boolean;
|
||||
draft?: DraftReferenceDto;
|
||||
sendTime?: string;
|
||||
uploadOnly?: boolean;
|
||||
envelope?: EmailEnvelopeDto;
|
||||
}
|
||||
|
||||
export interface MessageListQueryDto {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
order?: string;
|
||||
search?: string;
|
||||
from?: string;
|
||||
subject?: string;
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
hasAttachment?: boolean;
|
||||
isRead?: boolean;
|
||||
isFlagged?: boolean;
|
||||
}
|
||||
|
||||
// Updated types based on actual API response
|
||||
export interface InboxMessage extends Record<string, unknown> {
|
||||
id: number;
|
||||
mailbox: string;
|
||||
thread: string;
|
||||
from: {
|
||||
address: string;
|
||||
name: string;
|
||||
};
|
||||
to: Array<{
|
||||
address: string;
|
||||
name: string;
|
||||
}>;
|
||||
cc: Array<{
|
||||
address: string;
|
||||
name: string;
|
||||
}>;
|
||||
bcc: Array<{
|
||||
address: string;
|
||||
name: string;
|
||||
}>;
|
||||
messageId: string;
|
||||
subject: string;
|
||||
date: string;
|
||||
idate: string;
|
||||
intro: string;
|
||||
attachments: boolean;
|
||||
attachmentsList: EmailAttachmentDto[];
|
||||
size: number;
|
||||
seen: boolean;
|
||||
deleted: boolean;
|
||||
flagged: boolean;
|
||||
draft: boolean;
|
||||
answered: boolean;
|
||||
forwarded: boolean;
|
||||
references: string[];
|
||||
contentType: {
|
||||
value: string;
|
||||
params: {
|
||||
boundary: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface InboxPager {
|
||||
page: number;
|
||||
limit: number;
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
prevPage: boolean;
|
||||
nextPage: boolean;
|
||||
}
|
||||
|
||||
export interface InboxResponse {
|
||||
statusCode: number;
|
||||
success: boolean;
|
||||
data: {
|
||||
pager: InboxPager;
|
||||
message: string;
|
||||
results: InboxMessage[];
|
||||
};
|
||||
}
|
||||
|
||||
// Legacy types (keeping for backward compatibility)
|
||||
export interface MessageDto {
|
||||
id: string;
|
||||
mailbox: string;
|
||||
uid: number;
|
||||
flags: string[];
|
||||
envelope: {
|
||||
date: string;
|
||||
subject: string;
|
||||
from: EmailRecipientDto[];
|
||||
to: EmailRecipientDto[];
|
||||
cc?: EmailRecipientDto[];
|
||||
bcc?: EmailRecipientDto[];
|
||||
messageId: string;
|
||||
inReplyTo?: string;
|
||||
references?: string[];
|
||||
};
|
||||
bodyStructure: Record<string, unknown>;
|
||||
source?: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
attachments?: EmailAttachmentDto[];
|
||||
headers?: EmailHeaderDto[];
|
||||
size: number;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface MessageListResponse {
|
||||
messages: MessageDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface UserMailboxMessageParamDto {
|
||||
messageId: string;
|
||||
}
|
||||
|
||||
// New interface based on actual API response
|
||||
export interface MessageDetailResponse {
|
||||
statusCode: number;
|
||||
success: boolean;
|
||||
data: {
|
||||
message: {
|
||||
success: boolean;
|
||||
id: number;
|
||||
mailbox: string;
|
||||
thread: string;
|
||||
user: string;
|
||||
envelope: {
|
||||
from: string;
|
||||
rcpt: Array<{
|
||||
value: string;
|
||||
formatted: string;
|
||||
}>;
|
||||
};
|
||||
from: {
|
||||
address: string;
|
||||
name: string;
|
||||
};
|
||||
to: Array<{
|
||||
address: string;
|
||||
name: string;
|
||||
}>;
|
||||
subject: string;
|
||||
messageId: string;
|
||||
date: string;
|
||||
idate: string;
|
||||
size: number;
|
||||
seen: boolean;
|
||||
deleted: boolean;
|
||||
flagged: boolean;
|
||||
draft: boolean;
|
||||
answered: boolean;
|
||||
forwarded: boolean;
|
||||
html: string[];
|
||||
text: string;
|
||||
attachments: EmailAttachmentDto[];
|
||||
references: string[];
|
||||
metaData: Record<string, unknown>;
|
||||
verificationResults: {
|
||||
tls: {
|
||||
name: string;
|
||||
standardName: string;
|
||||
version: string;
|
||||
};
|
||||
spf: string;
|
||||
dkim: string;
|
||||
arc: boolean;
|
||||
bimi: boolean;
|
||||
};
|
||||
contentType: {
|
||||
value: string;
|
||||
params: {
|
||||
boundary: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageDetail {
|
||||
success: boolean;
|
||||
id: number;
|
||||
mailbox: string;
|
||||
thread: string;
|
||||
user: string;
|
||||
envelope: {
|
||||
from: string;
|
||||
rcpt: Array<{
|
||||
value: string;
|
||||
formatted: string;
|
||||
}>;
|
||||
};
|
||||
from: {
|
||||
address: string;
|
||||
name: string;
|
||||
};
|
||||
to: Array<{
|
||||
address: string;
|
||||
name: string;
|
||||
}>;
|
||||
subject: string;
|
||||
messageId: string;
|
||||
date: string;
|
||||
idate: string;
|
||||
size: number;
|
||||
seen: boolean;
|
||||
deleted: boolean;
|
||||
flagged: boolean;
|
||||
draft: boolean;
|
||||
answered: boolean;
|
||||
forwarded: boolean;
|
||||
html: string[];
|
||||
text: string;
|
||||
attachments: EmailAttachmentDto[];
|
||||
references: string[];
|
||||
metaData: Record<string, unknown>;
|
||||
verificationResults: {
|
||||
tls: {
|
||||
name: string;
|
||||
standardName: string;
|
||||
version: string;
|
||||
};
|
||||
spf: string;
|
||||
dkim: string;
|
||||
arc: boolean;
|
||||
bimi: boolean;
|
||||
};
|
||||
contentType: {
|
||||
value: string;
|
||||
params: {
|
||||
boundary: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user