attachment + download attach + suggestion

This commit is contained in:
hamid zarghami
2025-07-21 16:31:38 +03:30
parent 60f459d33b
commit b31b64a7f9
14 changed files with 879 additions and 331 deletions
@@ -0,0 +1,40 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { EmailSuggestionsService } from "@/services/EmailSuggestionsService";
export const useEmailSuggestions = () => {
const [debouncedQuery, setDebouncedQuery] = useState("");
const { data: suggestionsData, isLoading, error } = useQuery({
queryKey: ["emailSuggestions", debouncedQuery],
queryFn: () =>
EmailSuggestionsService.getSuggestions({
query: debouncedQuery,
limit: 5,
}),
enabled: debouncedQuery.length >= 2,
staleTime: 1000 * 60 * 5, // 5 minutes
});
if (error) {
console.error("🔥 Query error:", error);
}
const searchSuggestions = (query: string) => {
// Debounce the actual search
const timeoutId = setTimeout(() => {
setDebouncedQuery(query);
}, 300);
return () => clearTimeout(timeoutId);
};
const suggestions = suggestionsData?.suggestions || [];
return {
suggestions,
searchSuggestions,
isLoading,
hasResults: suggestions.length > 0,
};
};
@@ -0,0 +1,262 @@
import { useState, useEffect } from "react";
import { useSharedStore } from "@/shared/store/sharedStore";
import { useAuthStore } from "@/pages/auth/store/AuthStore";
import {
useGetMessageDetail,
useSendEmail,
} from "@/pages/received/hooks/useEmailData";
import { SendEmailDto, EmailAttachmentDto } from "@/pages/received/types/Types";
import { useUpdateDraft, useSendDraft } from "@/pages/draft/hooks/useDraftData";
import { toast } from "@/components/Toast";
import { useEmailSuggestions } from "./useEmailSuggestions";
export const useNewMessage = () => {
const {
draftData,
setDraftData,
editingDraftId,
setEditingDraftId,
} = useSharedStore();
const { email: userEmail } = useAuthStore();
// Form state
const [toEmails, setToEmails] = useState<string[]>([]);
const [subject, setSubject] = useState("");
const [content, setContent] = useState("");
const [priority, setPriority] = useState("");
const [attachments, setAttachments] = useState<EmailAttachmentDto[]>([]);
// Loading states
const [isSending, setIsSending] = useState(false);
const [isSavingDraft, setIsSavingDraft] = useState(false);
// API hooks
const sendEmailMutation = useSendEmail();
const updateDraftMutation = useUpdateDraft();
const sendDraftMutation = useSendDraft();
// Email suggestions
const {
suggestions: emailSuggestions,
searchSuggestions,
} = useEmailSuggestions();
// Get draft detail if draftData exists
const { data: draftDetail } = useGetMessageDetail(
draftData?.id.toString() || "",
draftData?.mailbox || ""
);
// Load draft data into form
useEffect(() => {
if (draftDetail) {
const toEmailsList =
draftDetail.to?.map((recipient) => recipient.address) || [];
setToEmails(toEmailsList);
setSubject(draftDetail.subject || "");
const draftWithContent = (draftDetail as unknown) as {
html?: string;
text?: string;
intro: string;
};
const draftContent = ensureString(
draftWithContent.html || draftWithContent.text || draftWithContent.intro
);
setContent(draftContent);
setDraftData(null);
}
}, [draftDetail, setDraftData]);
const ensureString = (val: unknown): string => {
if (typeof val === "string") return val;
if (val === null || val === undefined) return "";
return String(val);
};
const isValidEmail = (email: string) => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(email);
};
// Function to search email suggestions
const searchEmailSuggestions = (query: string) => {
searchSuggestions(query);
};
const addEmail = (email: string) => {
if (!isValidEmail(email)) {
return;
}
if (toEmails.includes(email)) {
toast("این ایمیل قبلاً اضافه شده است", "error");
return;
}
setToEmails([...toEmails, email]);
};
const removeEmail = (emailToRemove: string) => {
setToEmails(toEmails.filter((email) => email !== emailToRemove));
};
const parseEmailAddresses = (emails: string[]) => {
return emails
.filter((email) => email.length > 0)
.map((email) => ({ address: email }));
};
const resetForm = () => {
setToEmails([]);
setSubject("");
setContent("");
setPriority("");
setAttachments([]);
};
const handleEmailAction = async (isDraft: boolean = false) => {
const isEditingDraft = !!editingDraftId;
if (!isDraft && (toEmails.length === 0 || !subject)) {
toast("لطفا تمام فیلدهای مورد نیاز را پر کنید", "error");
return;
}
if (
isDraft &&
toEmails.length === 0 &&
!subject &&
ensureString(content).trim() === ""
) {
toast("حداقل یکی از فیلدها باید پر باشد", "error");
return;
}
if (isDraft) {
setIsSavingDraft(true);
} else {
setIsSending(true);
}
try {
let messageId = "";
if (isEditingDraft) {
const draftUpdateData = {
to: parseEmailAddresses(toEmails),
subject,
html: ensureString(content),
text: ensureString(content).replace(/<[^>]*>/g, ""),
...(attachments.length > 0 && { attachments }),
};
if (isDraft) {
await updateDraftMutation.mutateAsync(
{
messageId: editingDraftId.toString(),
draftData: draftUpdateData,
},
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onSuccess: (data: any) => {
messageId = data.data?.messageId;
},
}
);
} else {
await updateDraftMutation.mutateAsync(
{
messageId: editingDraftId.toString(),
draftData: {
...draftUpdateData,
isDraft: true,
uploadOnly: true,
},
},
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onSuccess: (data: any) => {
messageId = data.data?.messageId;
},
}
);
await sendDraftMutation.mutateAsync(
messageId || editingDraftId.toString()
);
toast("پیام با موفقیت ارسال شد", "success");
}
resetForm();
setEditingDraftId(null);
return true;
} else {
const emailData: SendEmailDto = {
from: { address: userEmail || "sender@example.com" },
to: parseEmailAddresses(toEmails),
subject,
html: ensureString(content),
text: ensureString(content).replace(/<[^>]*>/g, ""),
...(attachments.length > 0 && { attachments }),
...(isDraft && { isDraft: true, uploadOnly: true }),
};
await sendEmailMutation.mutateAsync(emailData, {
onSuccess: (data) => {
toast(data?.data.message, "success");
},
});
resetForm();
return true;
}
} catch (error) {
console.error("Error in email action:", error);
toast("خطا در انجام عملیات", "error");
return false;
} finally {
setIsSending(false);
setIsSavingDraft(false);
}
};
const handleSend = async () => {
const result = await handleEmailAction(false);
return result || false;
};
const handleSaveDraft = async () => {
const result = await handleEmailAction(true);
return result || false;
};
return {
// Form state
toEmails,
subject,
content,
priority,
attachments,
emailSuggestions,
// Loading states
isSending,
isSavingDraft,
// Form handlers
addEmail,
removeEmail,
setSubject,
setContent,
setPriority,
setAttachments,
// Search functions
searchEmailSuggestions,
// Actions
handleSend,
handleSaveDraft,
resetForm,
};
};