"use client"; import { useCallback, useEffect, useRef } from "react"; import { useTranslations } from "next-intl"; import { EmailComposer, type ComposerDraftData } from "@/components/email/email-composer"; import { ErrorBoundary, ComposerErrorFallback } from "@/components/error"; import { useAuthStore } from "@/stores/auth-store"; import { useEmailStore } from "@/stores/email-store"; import { toast } from "@/stores/toast-store"; import { useProTabStore, type ProComposeTabData } from "@/stores/pro-tab-store"; import { debug } from "@/lib/debug"; interface ProComposeTabBodyProps { tabId: string; data: ProComposeTabData; } /** * Renders a standalone `` inside its own Pro tab. Sending, * draft autosave, and discard all flow through the shared `email-store`, so * the result is identical to composing inline in the mail page - the * composer is just hosted in its own tab instead of in the right pane. */ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) { const t = useTranslations(); const client = useAuthStore((s) => s.client); const sendEmail = useEmailStore((s) => s.sendEmail); const fetchEmails = useEmailStore((s) => s.fetchEmails); const fetchScheduledEmails = useEmailStore((s) => s.fetchScheduledEmails); const refreshScheduledMetadata = useEmailStore((s) => s.refreshScheduledMetadata); const selectedMailbox = useEmailStore((s) => s.selectedMailbox); const isScheduledView = useEmailStore((s) => s.isScheduledView); const closeTab = useProTabStore((s) => s.closeTab); const updateTabTitle = useProTabStore((s) => s.updateTabTitle); const updateComposeDraft = useProTabStore((s) => s.updateComposeDraft); // Keep stable references for the callbacks below so the composer's // `key={sessionId}` doesn't churn. const tabIdRef = useRef(tabId); tabIdRef.current = tabId; const handleScheduledSendCreated = useCallback(async () => { if (client) { await refreshScheduledMetadata(client); if (isScheduledView) await fetchScheduledEmails(client); } closeTab(tabIdRef.current); }, [client, refreshScheduledMetadata, isScheduledView, fetchScheduledEmails, closeTab]); const handleSend = useCallback(async (sendData: Parameters['onSend']>>[0]) => { if (!client) return; try { const result = await sendEmail( client, sendData.to, sendData.subject, sendData.body, sendData.cc, sendData.bcc, sendData.identityId, sendData.fromEmail, sendData.draftId, sendData.fromName, sendData.htmlBody, sendData.attachments, sendData.inReplyTo, sendData.references, sendData.delayedUntil, sendData.envelopeMailFrom, ); if (result.scheduled) { await handleScheduledSendCreated(); return; } // Mark the original message as $answered / $forwarded so the standard // viewer and list reflect the action (same behaviour as inline compose). if (data.sourceEmailId && (data.mode === 'reply' || data.mode === 'replyAll')) { try { await client.setKeyword(data.sourceEmailId, '$answered'); } catch (e) { debug.error('Failed to set $answered keyword:', e); } } else if (data.sourceEmailId && data.mode === 'forward') { try { await client.setKeyword(data.sourceEmailId, '$forwarded'); } catch (e) { debug.error('Failed to set $forwarded keyword:', e); } } // Refresh the currently-active mail list so the new sent message / // updated keyword status shows up. await fetchEmails(client, selectedMailbox); closeTab(tabIdRef.current); } catch (error) { console.error('Failed to send email:', error); toast.error(t('notifications.error_sending')); } }, [client, sendEmail, fetchEmails, selectedMailbox, closeTab, data.sourceEmailId, data.mode, t, handleScheduledSendCreated]); const handleClose = useCallback(() => { closeTab(tabIdRef.current); }, [closeTab]); const handleDiscardDraft = useCallback(async (draftId: string) => { if (!client) return; try { await client.deleteEmail(draftId); } catch (error) { console.error('Failed to discard draft:', error); } }, [client]); const handleSaveState = useCallback((state: ComposerDraftData) => { updateComposeDraft(tabIdRef.current, state); // Keep the tab title in sync with the working subject. const subject = state.subject?.trim() || t('email_composer.new_message'); updateTabTitle(tabIdRef.current, subject); }, [updateComposeDraft, updateTabTitle, t]); // On first mount, ensure the tab title matches whatever subject we were // initialised with (replies start with "Re: …", forwards with "Fwd: …"). useEffect(() => { const initialSubject = data.initialData?.subject?.trim() ?? data.replyTo?.subject ?? ''; const title = initialSubject || t('email_composer.new_message'); updateTabTitle(tabIdRef.current, title); // Run once on mount only. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (
); }