forward and reply
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { FC } from 'react'
|
||||
import { EmojiHappy } from 'iconsax-react'
|
||||
import Reply from './Reply'
|
||||
import Forward from './Forward'
|
||||
import { MessageDetail } from '@/pages/received/types/Types'
|
||||
|
||||
interface EmailActionsProps {
|
||||
messageDetail: MessageDetail
|
||||
className?: string
|
||||
showReactions?: boolean
|
||||
}
|
||||
|
||||
const EmailActions: FC<EmailActionsProps> = ({
|
||||
messageDetail,
|
||||
className = '',
|
||||
showReactions = true
|
||||
}) => {
|
||||
return (
|
||||
<div className={`flex mt-9 justify-end ${className}`}>
|
||||
<div className='flex gap-5 text-sm items-center'>
|
||||
{showReactions && (
|
||||
<EmojiHappy
|
||||
size={24}
|
||||
color='black'
|
||||
className='cursor-pointer hover:opacity-70 transition-opacity'
|
||||
/>
|
||||
)}
|
||||
|
||||
<Reply
|
||||
originalMessage={messageDetail}
|
||||
className='border-r border-border pr-5'
|
||||
onClose={() => { }}
|
||||
/>
|
||||
|
||||
<Forward
|
||||
originalMessage={messageDetail}
|
||||
className='border-r border-border pr-5'
|
||||
onClose={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmailActions
|
||||
@@ -12,20 +12,10 @@ const EmailEditor: FC<EmailEditorProps> = ({
|
||||
onChange,
|
||||
placeholder = ''
|
||||
}) => {
|
||||
const modules = {
|
||||
toolbar: [
|
||||
[{ header: '1' }, { header: '2' }, { font: [] }],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
['bold', 'italic', 'underline'],
|
||||
['link', 'image'],
|
||||
[{ align: [] }],
|
||||
['clean']
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<ReactQuill
|
||||
modules={modules}
|
||||
theme="snow"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -4,21 +4,23 @@ import Button from '@/components/Button'
|
||||
import Select from '@/components/Select'
|
||||
|
||||
interface EmailFormActionsProps {
|
||||
priority: string
|
||||
onPriorityChange: (priority: string) => void
|
||||
onSend: () => void
|
||||
onSaveDraft: () => void
|
||||
isSending: boolean
|
||||
isSavingDraft: boolean
|
||||
onClose?: () => void
|
||||
onSaveDraft?: () => void
|
||||
isSavingDraft?: boolean
|
||||
priority?: string
|
||||
onPriorityChange?: (priority: string) => void
|
||||
}
|
||||
|
||||
const EmailFormActions: FC<EmailFormActionsProps> = ({
|
||||
priority,
|
||||
onPriorityChange,
|
||||
onSend,
|
||||
onSaveDraft,
|
||||
isSending,
|
||||
isSavingDraft
|
||||
onClose,
|
||||
onSaveDraft,
|
||||
isSavingDraft = false,
|
||||
priority,
|
||||
onPriorityChange
|
||||
}) => {
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
@@ -30,22 +32,40 @@ const EmailFormActions: FC<EmailFormActionsProps> = ({
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-4 sm:flex-row pt-4'>
|
||||
<div>
|
||||
<Select
|
||||
items={priorityOptions}
|
||||
placeholder={t('new_message.select_priority')}
|
||||
className='xl:w-[190px] min-w-[130px]'
|
||||
value={priority}
|
||||
onChange={(e) => onPriorityChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/* Priority Selector */}
|
||||
{priority !== undefined && onPriorityChange && (
|
||||
<div>
|
||||
<Select
|
||||
items={priorityOptions}
|
||||
placeholder={t('new_message.select_priority')}
|
||||
className='xl:w-[190px] min-w-[130px]'
|
||||
value={priority}
|
||||
onChange={(e) => onPriorityChange(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={onSaveDraft}
|
||||
loading={isSavingDraft}
|
||||
/>
|
||||
{/* Save Draft Button */}
|
||||
{onSaveDraft && (
|
||||
<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={onSaveDraft}
|
||||
loading={isSavingDraft}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Close Button */}
|
||||
{onClose && !onSaveDraft && (
|
||||
<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('common.close')}
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Send Button */}
|
||||
<Button
|
||||
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
|
||||
label={t('new_message.send')}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ArrowDown2, CloseSquare } from 'iconsax-react'
|
||||
import AvatarImage from '@/assets/images/avatar.svg'
|
||||
import ForwardIcon from '@/assets/images/forward.svg'
|
||||
import EmailEditor from './EmailEditor'
|
||||
import EmailFormActions from './EmailFormActions'
|
||||
import EmailInput from './EmailInput'
|
||||
import { MessageDetail } from '@/pages/received/types/Types'
|
||||
import { useAuthStore } from '@/pages/auth/store/AuthStore'
|
||||
import { useSendEmail } from '@/pages/received/hooks/useEmailData'
|
||||
import { SendEmailDto } from '@/pages/received/types/Types'
|
||||
import { toast } from '@/components/Toast'
|
||||
|
||||
interface ForwardProps {
|
||||
originalMessage: MessageDetail
|
||||
onClose: () => void
|
||||
onExpand?: () => void
|
||||
className?: string
|
||||
showInitialButton?: boolean
|
||||
}
|
||||
|
||||
const Forward: FC<ForwardProps> = ({
|
||||
originalMessage,
|
||||
onClose,
|
||||
onExpand,
|
||||
className = '',
|
||||
showInitialButton = true
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { email: userEmail } = useAuthStore()
|
||||
const sendEmailMutation = useSendEmail()
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState<boolean>(false)
|
||||
const [toEmails, setToEmails] = useState<string[]>([])
|
||||
const [content, setContent] = useState<string>('')
|
||||
|
||||
const expandForward = () => {
|
||||
setIsExpanded(true)
|
||||
// Pre-fill content with original message
|
||||
const forwardContent = `
|
||||
<br/><br/>
|
||||
<div dir="rtl">
|
||||
---------- پیام ارسالی ----------<br/>
|
||||
<strong>از:</strong> ${originalMessage.from.name || originalMessage.from.address} <${originalMessage.from.address}><br/>
|
||||
<strong>تاریخ:</strong> ${originalMessage.date}<br/>
|
||||
<strong>موضوع:</strong> ${originalMessage.subject}<br/>
|
||||
<strong>به:</strong> ${originalMessage.to.map(recipient => recipient.address).join(', ')}<br/>
|
||||
<br/>
|
||||
${originalMessage.html ? originalMessage.html.join('') : originalMessage.text}
|
||||
</div>
|
||||
`
|
||||
setContent(forwardContent)
|
||||
onExpand?.()
|
||||
}
|
||||
|
||||
const handleClose = async () => {
|
||||
// اگر محتوا یا ایمیل وارد شده، پیشنویس ذخیره کن
|
||||
if (content.trim() || toEmails.length > 0) {
|
||||
// TODO: Implement save draft functionality
|
||||
toast('پیشنویس ذخیره شد', 'success')
|
||||
}
|
||||
|
||||
// بسته کردن و برگشت به حالت قبل
|
||||
collapseForward()
|
||||
onClose() // این خط مهمه تا parent component بفهمه
|
||||
}
|
||||
|
||||
const collapseForward = () => {
|
||||
setIsExpanded(false)
|
||||
resetForm()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
setToEmails([])
|
||||
setContent('')
|
||||
setIsExpanded(false)
|
||||
}
|
||||
|
||||
const handleSendForward = async () => {
|
||||
if (toEmails.length === 0) {
|
||||
toast('لطفا حداقل یک گیرنده وارد کنید', 'error')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!content.trim()) {
|
||||
toast('لطفا متن پیام را وارد کنید', 'error')
|
||||
return false
|
||||
}
|
||||
|
||||
// Format date to Persian
|
||||
const originalDate = new Date(originalMessage.date)
|
||||
const persianDate = new Intl.DateTimeFormat('fa-IR', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(originalDate)
|
||||
|
||||
const htmlContent =
|
||||
content +
|
||||
"<br /><br />" +
|
||||
"<div dir='rtl'>---------- پیام ارسالی ----------<br/>" +
|
||||
`<strong>از:</strong> ${originalMessage.from.name || originalMessage.from.address} <${originalMessage.from.address}><br/>` +
|
||||
`<strong>تاریخ:</strong> ${persianDate}<br/>` +
|
||||
`<strong>موضوع:</strong> ${originalMessage.subject}<br/>` +
|
||||
`<strong>به:</strong> ${originalMessage.to.map(recipient => recipient.address).join(', ')}<br/>` +
|
||||
"<br/>" +
|
||||
(originalMessage?.html?.[0] || originalMessage?.text || '') + "</div>"
|
||||
|
||||
const forwardData: SendEmailDto = {
|
||||
from: { address: userEmail || 'sender@example.com' },
|
||||
to: toEmails.map(email => ({ address: email })),
|
||||
subject: originalMessage.subject.startsWith('Fwd:')
|
||||
? originalMessage.subject
|
||||
: `Fwd: ${originalMessage.subject}`,
|
||||
html: htmlContent,
|
||||
text: content.replace(/<[^>]*>/g, ''),
|
||||
isForward: true,
|
||||
}
|
||||
|
||||
try {
|
||||
await sendEmailMutation.mutateAsync(forwardData)
|
||||
toast('پیام با موفقیت ارسال شد', 'success')
|
||||
resetForm()
|
||||
onClose?.()
|
||||
return true
|
||||
} catch {
|
||||
const errorMessage = 'خطا در ارسال پیام'
|
||||
toast(errorMessage, 'error')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// اگر showInitialButton true باشه و هنوز expand نشده، فقط دکمه نشون بده
|
||||
if (showInitialButton && !isExpanded) {
|
||||
return (
|
||||
<div className={`flex gap-5 text-sm ${className}`}>
|
||||
<div
|
||||
onClick={expandForward}
|
||||
className='flex gap-2 cursor-pointer items-center border-r border-border pr-5 hover:text-primary transition-colors'
|
||||
>
|
||||
<img src={ForwardIcon} className='w-[17px]' />
|
||||
<div>{t('mail.forward')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// در غیر این صورت editor رو نشون بده
|
||||
// (یا showInitialButton false هست یا isExpanded true هست)
|
||||
|
||||
return (
|
||||
<div className={`mt-6 w-full ${className}`}>
|
||||
{/* Forward Header */}
|
||||
<div className='flex items-center justify-between mb-4 p-3 bg-gray-50 rounded-lg border'>
|
||||
<div className='flex items-center gap-2 text-sm text-gray-600'>
|
||||
<img src={ForwardIcon} className='w-4 h-4' />
|
||||
<span>ارسال به دیگران</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<button
|
||||
onClick={collapseForward}
|
||||
className='p-1 hover:bg-gray-200 rounded transition-colors'
|
||||
title='بستن'
|
||||
>
|
||||
<ArrowDown2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
collapseForward()
|
||||
onClose?.()
|
||||
}}
|
||||
className='p-1 hover:bg-gray-200 rounded transition-colors'
|
||||
title='لغو'
|
||||
>
|
||||
<CloseSquare size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Forward Content */}
|
||||
<div className='flex gap-4'>
|
||||
<img src={AvatarImage} className='size-10 flex-shrink-0' />
|
||||
<div className='w-full'>
|
||||
{/* To Field */}
|
||||
<div className='mb-4'>
|
||||
<EmailInput
|
||||
toEmails={toEmails}
|
||||
onAddEmail={(email) => {
|
||||
if (!toEmails.includes(email)) {
|
||||
setToEmails([...toEmails, email])
|
||||
}
|
||||
}}
|
||||
onRemoveEmail={(email) => {
|
||||
setToEmails(toEmails.filter(e => e !== email))
|
||||
}}
|
||||
suggestions={[]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Subject Field */}
|
||||
<div className='mb-4 text-sm'>
|
||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded border'>
|
||||
<span className='text-gray-600 min-w-fit'>موضوع:</span>
|
||||
<span className='text-gray-800'>
|
||||
{originalMessage.subject.startsWith('Fwd:')
|
||||
? originalMessage.subject
|
||||
: `Fwd: ${originalMessage.subject}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editor */}
|
||||
<div className='mb-4'>
|
||||
<EmailEditor
|
||||
value={content}
|
||||
onChange={setContent}
|
||||
placeholder='پیام خود را اضافه کنید...'
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<EmailFormActions
|
||||
onSend={handleSendForward}
|
||||
isSending={sendEmailMutation.isPending}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Forward
|
||||
@@ -95,7 +95,7 @@ const NewMessage: FC = () => {
|
||||
}`}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<div className={`fixed left-2 right-2 bottom-2 md:left-4 md:right-4 md:bottom-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 w-auto xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
|
||||
<div className={`fixed left-2 bottom-2 md:left-4 md:bottom-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 w-auto xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
|
||||
}`}>
|
||||
{/* Header */}
|
||||
<div className='flex justify-between items-center mb-6 md:mb-8'>
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ArrowDown2, CloseSquare } from 'iconsax-react'
|
||||
import AvatarImage from '@/assets/images/avatar.svg'
|
||||
import AnswerIcon from '@/assets/images/answer.svg'
|
||||
import EmailEditor from './EmailEditor'
|
||||
import EmailFormActions from './EmailFormActions'
|
||||
import { useReply } from './hooks/useReply'
|
||||
import { MessageDetail } from '@/pages/received/types/Types'
|
||||
import { toast } from '@/components/Toast'
|
||||
|
||||
interface ReplyProps {
|
||||
originalMessage: MessageDetail
|
||||
onClose?: () => void
|
||||
onExpand?: () => void
|
||||
className?: string
|
||||
showInitialButton?: boolean
|
||||
}
|
||||
|
||||
const Reply: FC<ReplyProps> = ({
|
||||
originalMessage,
|
||||
onClose,
|
||||
onExpand,
|
||||
className = '',
|
||||
showInitialButton = true
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
content,
|
||||
isExpanded,
|
||||
isSending,
|
||||
setContent,
|
||||
expandReply,
|
||||
collapseReply,
|
||||
handleSendReply,
|
||||
handleSaveDraft
|
||||
} = useReply({
|
||||
originalMessage,
|
||||
onSuccess: () => {
|
||||
onClose?.()
|
||||
}
|
||||
})
|
||||
|
||||
const handleExpand = () => {
|
||||
expandReply()
|
||||
onExpand?.()
|
||||
}
|
||||
|
||||
|
||||
const handleClose = async () => {
|
||||
// اگر محتوایی وارد شده، پیشنویس ذخیره کن
|
||||
if (content.trim()) {
|
||||
await handleSaveDraft()
|
||||
toast('پیشنویس ذخیره شد', 'success')
|
||||
}
|
||||
|
||||
// بسته کردن و برگشت به حالت قبل
|
||||
collapseReply()
|
||||
onClose?.() // این خط مهمه تا parent component بفهمه
|
||||
}
|
||||
|
||||
// اگر showInitialButton true باشه و هنوز expand نشده، فقط دکمه نشون بده
|
||||
if (showInitialButton && !isExpanded) {
|
||||
return (
|
||||
<div className={`flex justify-end ${className}`}>
|
||||
<div className='flex gap-5 text-sm'>
|
||||
<div
|
||||
onClick={handleExpand}
|
||||
className='flex gap-2 cursor-pointer items-center hover:text-primary transition-colors'
|
||||
>
|
||||
<img src={AnswerIcon} className='w-[17px]' />
|
||||
<div>{t('mail.answer')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSend = () => {
|
||||
handleSendReply(originalMessage?.html[0])
|
||||
collapseReply()
|
||||
}
|
||||
|
||||
// در غیر این صورت editor رو نشون بده
|
||||
// (یا showInitialButton false هست یا isExpanded true هست)
|
||||
|
||||
return (
|
||||
<div className={`mt-6 w-full ${className}`}>
|
||||
{/* Reply Header */}
|
||||
<div className='flex items-center justify-between mb-4 p-3 bg-gray-50 rounded-lg border'>
|
||||
<div className='flex items-center gap-2 text-sm text-gray-600'>
|
||||
<img src={AnswerIcon} className='w-4 h-4' />
|
||||
<span>پاسخ به {originalMessage.from.name || originalMessage.from.address}</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<button
|
||||
onClick={collapseReply}
|
||||
className='p-1 hover:bg-gray-200 rounded transition-colors'
|
||||
title='بستن'
|
||||
>
|
||||
<ArrowDown2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
collapseReply()
|
||||
onClose?.()
|
||||
}}
|
||||
className='p-1 hover:bg-gray-200 rounded transition-colors'
|
||||
title='لغو'
|
||||
>
|
||||
<CloseSquare size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reply Content */}
|
||||
<div className='flex gap-4'>
|
||||
<img src={AvatarImage} className='size-10 flex-shrink-0' />
|
||||
<div className='w-full'>
|
||||
{/* To Field */}
|
||||
<div className='mb-4 text-sm'>
|
||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded border'>
|
||||
<span className='text-gray-600 min-w-fit'>به:</span>
|
||||
<span className='text-gray-800'>{originalMessage.from.address}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subject Field */}
|
||||
<div className='mb-4 text-sm'>
|
||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded border'>
|
||||
<span className='text-gray-600 min-w-fit'>موضوع:</span>
|
||||
<span className='text-gray-800'>
|
||||
{originalMessage.subject.startsWith('Re:')
|
||||
? originalMessage.subject
|
||||
: `Re: ${originalMessage.subject}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editor */}
|
||||
<div className='mb-4'>
|
||||
<EmailEditor
|
||||
value={content}
|
||||
onChange={setContent}
|
||||
placeholder='پیام خود را بنویسید...'
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<EmailFormActions
|
||||
onSend={handleSend}
|
||||
onClose={handleClose}
|
||||
isSending={isSending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Reply
|
||||
@@ -0,0 +1,137 @@
|
||||
import { useState } from "react";
|
||||
import { useAuthStore } from "@/pages/auth/store/AuthStore";
|
||||
import { useSendEmail } from "@/pages/received/hooks/useEmailData";
|
||||
import { SendEmailDto } from "@/pages/received/types/Types";
|
||||
import { MessageDetail } from "@/pages/received/types/Types";
|
||||
import { toast } from "@/components/Toast";
|
||||
|
||||
interface UseReplyProps {
|
||||
originalMessage: MessageDetail;
|
||||
onSuccess?: () => void;
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
export const useReply = ({
|
||||
originalMessage,
|
||||
onSuccess,
|
||||
onError,
|
||||
}: UseReplyProps) => {
|
||||
const { email: userEmail } = useAuthStore();
|
||||
const [content, setContent] = useState<string>("");
|
||||
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
||||
|
||||
// API hooks
|
||||
const sendEmailMutation = useSendEmail();
|
||||
|
||||
const resetForm = () => {
|
||||
setContent("");
|
||||
setIsExpanded(false);
|
||||
};
|
||||
|
||||
const handleSendReply = async (html: string) => {
|
||||
if (!content.trim()) {
|
||||
toast("لطفا متن پیام را وارد کنید", "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!originalMessage) {
|
||||
toast("اطلاعات پیام یافت نشد", "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Format date to Persian
|
||||
const originalDate = new Date(originalMessage.date);
|
||||
const persianDate = new Intl.DateTimeFormat("fa-IR", {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(originalDate);
|
||||
|
||||
const htmlContent =
|
||||
content +
|
||||
"<br /><br />" +
|
||||
`<div dir="rtl">در تاریخ ${persianDate} ${originalMessage.from.name} <${originalMessage.from.address}> نوشت:</div>` +
|
||||
"<br /><br />" +
|
||||
`<div dir="rtl">${html}</div>`;
|
||||
|
||||
const replyData: SendEmailDto = {
|
||||
from: { address: userEmail || "sender@example.com" },
|
||||
to: [
|
||||
{
|
||||
address: originalMessage.from.address,
|
||||
name: originalMessage.from.name,
|
||||
},
|
||||
],
|
||||
replyTo: {
|
||||
name: originalMessage.from.name,
|
||||
address: originalMessage.from.address,
|
||||
},
|
||||
subject: originalMessage.subject.startsWith("Re:")
|
||||
? originalMessage.subject
|
||||
: `Re: ${originalMessage.subject}`,
|
||||
html: htmlContent,
|
||||
text: content.replace(/<[^>]*>/g, ""),
|
||||
};
|
||||
|
||||
try {
|
||||
await sendEmailMutation.mutateAsync(replyData);
|
||||
toast("پاسخ با موفقیت ارسال شد", "success");
|
||||
resetForm();
|
||||
onSuccess?.();
|
||||
return true;
|
||||
} catch {
|
||||
const errorMessage = "خطا در ارسال پاسخ";
|
||||
toast(errorMessage, "error");
|
||||
onError?.(errorMessage);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveDraft = async () => {
|
||||
if (!content.trim()) {
|
||||
toast("متن پیام خالی است", "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Implement save draft functionality
|
||||
toast("پیشنویس ذخیره شد", "success");
|
||||
return true;
|
||||
} catch {
|
||||
const errorMessage = "خطا در ذخیره پیشنویس";
|
||||
toast(errorMessage, "error");
|
||||
onError?.(errorMessage);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const expandReply = () => {
|
||||
setIsExpanded(true);
|
||||
};
|
||||
|
||||
const collapseReply = () => {
|
||||
setIsExpanded(false);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
content,
|
||||
isExpanded,
|
||||
isSending: sendEmailMutation.isPending,
|
||||
|
||||
// Handlers
|
||||
setContent,
|
||||
expandReply,
|
||||
collapseReply,
|
||||
handleSendReply,
|
||||
handleSaveDraft,
|
||||
resetForm,
|
||||
|
||||
// Original message data
|
||||
originalMessage,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user