From 4cf56589ee2515d276543e6483bd171b41994a3c Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Fri, 10 Apr 2026 12:54:10 -0400 Subject: [PATCH 1/4] remove .vscode --- .gitignore | 3 ++- .vscode/settings.json | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 831aefc..3a329a1 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ todo.txt /scripts/sync-db.js /scripts/update-env.js /scripts/update-env.js -/scripts/README.md \ No newline at end of file +/scripts/README.md +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0967ef4..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} From 2ef5f2e006acdc2d1a88ca27e55d28140797697c Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Mon, 13 Apr 2026 00:06:12 -0400 Subject: [PATCH 2/4] vacation responder settings working --- pages/api/gmail/initiate.ts | 1 + pages/api/gmail/settings.ts | 38 +++++++++++++++++++++++++++ pages/panel/email/settings.tsx | 47 ++++++++++++++++++++++++---------- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/pages/api/gmail/initiate.ts b/pages/api/gmail/initiate.ts index 594504d..44da08c 100644 --- a/pages/api/gmail/initiate.ts +++ b/pages/api/gmail/initiate.ts @@ -12,6 +12,7 @@ const SCOPES = [ "https://www.googleapis.com/auth/gmail.modify", "https://www.googleapis.com/auth/gmail.send", "https://www.googleapis.com/auth/contacts.readonly", + "https://www.googleapis.com/auth/gmail.settings.basic", ]; function resolveRuntimeBaseUrl(req: NextApiRequest) { diff --git a/pages/api/gmail/settings.ts b/pages/api/gmail/settings.ts index 18be47d..9e4a523 100644 --- a/pages/api/gmail/settings.ts +++ b/pages/api/gmail/settings.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { prisma } from "@/lib/prisma"; import { requireSession } from "@/lib/auth"; +import { getValidGmailAccessToken } from "@/lib/gmail/tokens"; function asStr(v: unknown) { return typeof v === "string" ? v.trim() : ""; @@ -104,6 +105,43 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) clickTrackingEnabled: Boolean(req.body?.clickTrackingEnabled ?? true), }, }); + + const tokenResult = await getValidGmailAccessToken(userId, accountEmail); + if (!tokenResult.ok) { + res.status(tokenResult.reason === "account_not_found" ? 404 : 401).json({ message: tokenResult.message }); + return; + } + + const setVacationWithToken = async (accessToken: string) => fetch("https://gmail.googleapis.com/gmail/v1/users/me/settings/vacation", { + method: "PUT", + headers: { + "Authorization": `Bearer ${accessToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + enableAutoReply: req.body?.vacationResponderEnabled, + responseSubject: req.body?.vacationSubject, + responseBodyPlainText: req.body?.vacationBodyText, + responseBodyHtml: req.body?.vacationBodyHtml, + startTime: req.body?.vacationStartAt, + endTime: req.body?.vacationEndAt + }) + }); + + let vacationResult = await setVacationWithToken(tokenResult.accessToken); + if (vacationResult.status == 401) { + const refreshResult = await getValidGmailAccessToken(userId, accountEmail, { forceRefresh: true }); + if (!refreshResult.ok) { + res.status(401).json({ message: refreshResult.message }); + return; + } + vacationResult = await setVacationWithToken(refreshResult.accessToken); + } + if (!vacationResult.ok) { + res.status(502).json({ message: "Failed to update vacation settings" }); + return; + } + res.status(200).json({ settings: updated }); return; } diff --git a/pages/panel/email/settings.tsx b/pages/panel/email/settings.tsx index 66f751e..a007d0f 100644 --- a/pages/panel/email/settings.tsx +++ b/pages/panel/email/settings.tsx @@ -192,6 +192,9 @@ const EmailSettingsPage: React.FC = ({ userRole }) => { }); const [providerAccounts, setProviderAccounts] = useState([]); const [blockedSenders, setBlockedSenders] = useState([]); + /** Baseline for tracking / vacation / contact visibility — updated after load and successful save. */ + const [savedSettings, setSavedSettings] = useState(null); + const [savedContactVisibility, setSavedContactVisibility] = useState(null); const [testingProviderId, setTestingProviderId] = useState(""); const [newProvider, setNewProvider] = useState({ accountEmail: "", @@ -230,6 +233,14 @@ const EmailSettingsPage: React.FC = ({ userRole }) => { return "/panel/email"; }, [connectedAccounts]); + const hasUnsavedSettingsChanges = useMemo(() => { + if (!primaryAccountEmail || !savedSettings || !savedContactVisibility) return false; + return ( + JSON.stringify(settings) !== JSON.stringify(savedSettings) || + JSON.stringify(contactVisibility) !== JSON.stringify(savedContactVisibility) + ); + }, [primaryAccountEmail, settings, contactVisibility, savedSettings, savedContactVisibility]); + const loadAccounts = useCallback(async () => { const response = await fetch("/api/gmail/status"); const payload = await response.json().catch(() => ({})); @@ -269,7 +280,7 @@ const EmailSettingsPage: React.FC = ({ userRole }) => { const blockedPayload = await blockedRes.json().catch(() => ({})); const rawSettings = settingsPayload?.settings || {}; - setSettings({ + const nextSettings: Settings = { trackingEnabled: Boolean(rawSettings.trackingEnabled), openTrackingEnabled: Boolean(rawSettings.openTrackingEnabled ?? true), clickTrackingEnabled: Boolean(rawSettings.clickTrackingEnabled ?? true), @@ -278,16 +289,20 @@ const EmailSettingsPage: React.FC = ({ userRole }) => { vacationBodyText: String(rawSettings.vacationBodyText || ""), vacationStartAt: rawSettings.vacationStartAt ? String(rawSettings.vacationStartAt).slice(0, 16) : "", vacationEndAt: rawSettings.vacationEndAt ? String(rawSettings.vacationEndAt).slice(0, 16) : "", - }); + }; + setSettings(nextSettings); setSignatures(Array.isArray(signaturesPayload?.signatures) ? signaturesPayload.signatures : []); setTemplates(Array.isArray(templatesPayload?.templates) ? templatesPayload.templates : []); setContactTags(Array.isArray(tagsPayload?.tags) ? tagsPayload.tags : []); const visibility = visibilityPayload?.visibility || {}; - setContactVisibility({ + const nextVisibility: ContactVisibility = { showPhone: Boolean(visibility.showPhone ?? true), showBusiness: Boolean(visibility.showBusiness ?? true), showWebsite: Boolean(visibility.showWebsite ?? true), - }); + }; + setContactVisibility(nextVisibility); + setSavedSettings(nextSettings); + setSavedContactVisibility(nextVisibility); setProviderAccounts(Array.isArray(providersPayload?.accounts) ? providersPayload.accounts : []); setBlockedSenders(Array.isArray(blockedPayload?.blocked) ? blockedPayload.blocked : []); } finally { @@ -413,6 +428,8 @@ const EmailSettingsPage: React.FC = ({ userRole }) => { }), ]); if (!settingsRes.ok || !visibilityRes.ok) throw new Error("Failed to save settings"); + setSavedSettings({ ...settings }); + setSavedContactVisibility({ ...contactVisibility }); setStatus("Settings saved."); } catch (error) { setStatus(error instanceof Error ? error.message : "Failed to save settings"); @@ -880,21 +897,25 @@ const EmailSettingsPage: React.FC = ({ userRole }) => { -
-
- + {(hasUnsavedSettingsChanges || status) ? ( +
+ {hasUnsavedSettingsChanges ? ( + + ) : null} {status ? ( {status} ) : null}
-

- Applies tracking, vacation, and contact visibility for your user account (synced across connected addresses where applicable). -

-
+ ) : null}
)} From 9684b53fc230acf5caf11c55f925a71510f19b30 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Mon, 13 Apr 2026 00:21:54 -0400 Subject: [PATCH 3/4] updates by Alex --- .../PanelPages/EmailingPlatformSection.tsx | 855 ++++++++++++++---- components/email/ComposeRichEditor.tsx | 338 +++++++ pages/api/gmail/send.ts | 199 +++- 3 files changed, 1193 insertions(+), 199 deletions(-) create mode 100644 components/email/ComposeRichEditor.tsx diff --git a/components/PanelPages/EmailingPlatformSection.tsx b/components/PanelPages/EmailingPlatformSection.tsx index bfcfe14..416e4b4 100644 --- a/components/PanelPages/EmailingPlatformSection.tsx +++ b/components/PanelPages/EmailingPlatformSection.tsx @@ -6,14 +6,23 @@ import { FiAlertCircle, FiArchive, FiCheckSquare, + FiCheck, + FiChevronDown, FiChevronsRight, FiCornerUpLeft, FiDownload, FiEdit3, FiFilter, + FiFileText, + FiImage, FiInbox, + FiLink, + FiPaperclip, + FiPrinter, FiKey, FiMail, + FiMaximize2, + FiMinimize2, FiMove, FiPlus, FiRefreshCw, @@ -22,17 +31,37 @@ import { FiUpload, FiUserPlus, FiTrash2, + FiType, FiUsers, FiX, } from "react-icons/fi"; import RowActionMenu, { RowActionMenuItem } from "@/components/ui/RowActionMenu"; import SuccessStatusModal from "@/components/ui/SuccessStatusModal"; import ConfirmActionModal from "@/components/ui/ConfirmActionModal"; +import ComposeRichEditor, { printComposeContent, type ComposeRichEditorHandle } from "@/components/email/ComposeRichEditor"; const inter = Inter({ subsets: ["latin"] }); const PAGE_SIZE = 50; const CONTACTS_PAGE_SIZE = 50; +/** Neutral scrollbar; overrides global purple `::-webkit-scrollbar` in app/globals.css for compose UI */ + +/** Basic validation for comma-separated recipient fields (Cc / Bcc). */ +function validateRecipientCsv(label: "Cc" | "Bcc", raw: string): string | null { + const trimmed = raw.trim(); + if (!trimmed) return null; + const parts = trimmed.split(",").map((entry) => entry.trim()).filter(Boolean); + const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + for (const part of parts) { + const addr = part.includes("<") ? (part.match(/<([^>]+)>/)?.[1] || part).trim() : part; + if (!emailOk.test(addr)) return `${label}: invalid address "${part}"`; + } + return null; +} + +const COMPOSE_NEUTRAL_SCROLLBAR = + "[scrollbar-width:thin] [scrollbar-color:rgb(203_213_225)_rgb(241_245_249)] [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:rounded-full [&::-webkit-scrollbar-track]:bg-slate-100 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-slate-300 [&::-webkit-scrollbar-thumb:hover]:bg-slate-400"; + type ModuleKey = | "inbox" | "sent" @@ -296,6 +325,20 @@ const EmailingPlatformSection: React.FC = ({ const [showBcc, setShowBcc] = useState(false); const [composeSubject, setComposeSubject] = useState(""); const [composeBody, setComposeBody] = useState(""); + const [composeBodyHtml, setComposeBodyHtml] = useState(""); + const [composeAttachments, setComposeAttachments] = useState< + Array<{ id: string; filename: string; contentType: string; contentBase64: string }> + >([]); + const [composeTemplates, setComposeTemplates] = useState< + Array<{ id: string; name: string; subject: string | null; bodyHtml: string | null; bodyText: string | null }> + >([]); + const [composeTemplateMenuOpen, setComposeTemplateMenuOpen] = useState(false); + const [saveTemplateModalOpen, setSaveTemplateModalOpen] = useState(false); + const [saveTemplateName, setSaveTemplateName] = useState(""); + const [saveTemplateSaving, setSaveTemplateSaving] = useState(false); + const composeEditorRef = useRef(null); + const composeAttachInputRef = useRef(null); + const [composeFormattingToolbarOpen, setComposeFormattingToolbarOpen] = useState(false); const [composeAccountEmail, setComposeAccountEmail] = useState(""); const [composeThreadId, setComposeThreadId] = useState(""); const [composeInReplyTo, setComposeInReplyTo] = useState(""); @@ -303,6 +346,8 @@ const EmailingPlatformSection: React.FC = ({ const [sendingCompose, setSendingCompose] = useState(false); const [composeError, setComposeError] = useState(""); const [composeSuccess, setComposeSuccess] = useState(""); + const [sentToastMessage, setSentToastMessage] = useState(null); + const sentToastTimerRef = useRef | null>(null); const [composeExpanded, setComposeExpanded] = useState(false); const [composeActiveDraftKey, setComposeActiveDraftKey] = useState(""); const [inlineComposeMode, setInlineComposeMode] = useState(null); @@ -515,6 +560,21 @@ const EmailingPlatformSection: React.FC = ({ setIsEditContactModalOpen(false); }, [editingContact]); + const showSentToast = useCallback((message: string) => { + setSentToastMessage(message); + if (sentToastTimerRef.current) clearTimeout(sentToastTimerRef.current); + sentToastTimerRef.current = setTimeout(() => { + setSentToastMessage(null); + sentToastTimerRef.current = null; + }, 4500); + }, []); + + useEffect(() => { + return () => { + if (sentToastTimerRef.current) clearTimeout(sentToastTimerRef.current); + }; + }, []); + useEffect(() => { selectedMessageIdRef.current = selectedMessageId; }, [selectedMessageId]); @@ -967,6 +1027,22 @@ const EmailingPlatformSection: React.FC = ({ }, [composeAccountEmail, connectedAccounts, selectedAccounts]); const effectiveComposeDraftStorageKey = composeActiveDraftKey || composeDraftStorageKey; + useEffect(() => { + if (!isComposeOpen || !composeAccountEmail) return; + let cancelled = false; + void fetch(`/api/gmail/templates?accountEmail=${encodeURIComponent(composeAccountEmail)}`) + .then((r) => r.json()) + .then((payload) => { + if (cancelled) return; + const list = Array.isArray(payload?.templates) ? payload.templates : []; + setComposeTemplates(list); + }) + .catch(() => null); + return () => { + cancelled = true; + }; + }, [isComposeOpen, composeAccountEmail]); + const inlineDraftStorageKey = useMemo(() => { if (!inlineComposeMode || !selectedMessage) return ""; if (inlineComposeMode === "forward") return ""; @@ -1482,7 +1558,9 @@ const EmailingPlatformSection: React.FC = ({ const flushDraftsNow = useCallback(() => { if (!sendingCompose && isComposeOpen && effectiveComposeDraftStorageKey) { - const hasComposeContent = composeBody.trim(); + const hasComposeContent = + composeBody.trim() || + composeBodyHtml.replace(/<[^>]+>/g, "").replace(/ /gi, " ").trim(); if (hasComposeContent) { void saveLocalDraft( effectiveComposeDraftStorageKey, @@ -1494,6 +1572,7 @@ const EmailingPlatformSection: React.FC = ({ showBcc, subject: composeSubject, bodyText: composeBody, + bodyHtml: composeBodyHtml, accountEmail: composeAccountEmail, updatedAt: Date.now(), }, @@ -1527,6 +1606,7 @@ const EmailingPlatformSection: React.FC = ({ composeAccountEmail, composeBcc, composeBody, + composeBodyHtml, composeCc, composeSubject, composeTo, @@ -1573,7 +1653,9 @@ const EmailingPlatformSection: React.FC = ({ useEffect(() => { if (sendingCompose || !isComposeOpen || !effectiveComposeDraftStorageKey) return; - const hasContent = composeBody.trim(); + const hasContent = + composeBody.trim() || + composeBodyHtml.replace(/<[^>]+>/g, "").replace(/ /gi, " ").trim(); const timeout = window.setTimeout(() => { if (!hasContent) return; void saveLocalDraft(effectiveComposeDraftStorageKey, { @@ -1584,6 +1666,7 @@ const EmailingPlatformSection: React.FC = ({ showBcc, subject: composeSubject, bodyText: composeBody, + bodyHtml: composeBodyHtml, accountEmail: composeAccountEmail, updatedAt: Date.now(), }); @@ -1594,6 +1677,7 @@ const EmailingPlatformSection: React.FC = ({ composeAccountEmail, composeBcc, composeBody, + composeBodyHtml, composeCc, composeActiveDraftKey, composeDraftStorageKey, @@ -1670,6 +1754,9 @@ const EmailingPlatformSection: React.FC = ({ setShowBcc(Boolean(draftMessage.composeShowBcc)); setComposeSubject(draftMessage.subject || ""); setComposeBody(draftMessage.composeBodyText || ""); + setComposeBodyHtml(draftMessage.composeBodyHtml || ""); + setComposeAttachments([]); + setComposeFormattingToolbarOpen(false); setComposeAccountEmail(accountEmail); setComposeThreadId(draftMessage.threadId || ""); setComposeInReplyTo(draftMessage.messageIdHeader || ""); @@ -1904,7 +1991,7 @@ ${sourceText}`; }; const sendInlineCompose = async () => { - if (!selectedMessage || !inlineComposeTo.trim() || !inlineComposeSubject.trim() || inlineComposeSending) return; + if (!selectedMessage || !inlineComposeTo.trim() || inlineComposeSending) return; const intro = inlineComposeIntroText.trim(); const textBody = intro ? `${intro}\n\n${inlineComposeBodyText}` : inlineComposeBodyText || intro; if (!textBody.trim()) return; @@ -1941,7 +2028,7 @@ ${sourceText}`; () => null ); } - setInlineComposeSuccess("Sent."); + showSentToast("Message Sent"); setInlineComposeMode(null); await Promise.all([loadMessages(), loadMailboxCounts(), loadUnreadBadges()]); setDetailError(""); @@ -2005,6 +2092,10 @@ ${sourceText}`; setShowBcc(false); setComposeSubject(""); setComposeBody(""); + setComposeBodyHtml(""); + setComposeAttachments([]); + setComposeTemplateMenuOpen(false); + setComposeFormattingToolbarOpen(false); setComposeAccountEmail(defaultAccount); setComposeThreadId(""); setComposeInReplyTo(""); @@ -2017,7 +2108,15 @@ ${sourceText}`; }; const handleSendCompose = async () => { - if (!composeTo.trim() || !composeSubject.trim() || !composeBody.trim() || !composeAccountEmail || sendingCompose) return; + const strippedHtml = composeBodyHtml.replace(/<[^>]+>/g, "").replace(/ /gi, " ").trim(); + const hasBody = Boolean(composeBody.trim() || strippedHtml); + if (!composeTo.trim() || !hasBody || !composeAccountEmail || sendingCompose) return; + const ccErr = validateRecipientCsv("Cc", composeCc); + const bccErr = validateRecipientCsv("Bcc", composeBcc); + if (ccErr || bccErr) { + setComposeError(ccErr || bccErr || ""); + return; + } setSendingCompose(true); setComposeError(""); setComposeSuccess(""); @@ -2032,12 +2131,18 @@ ${sourceText}`; bcc: composeBcc.trim(), subject: composeSubject.trim(), body: composeBody.trim(), + bodyHtml: composeBodyHtml.trim(), threadId: composeThreadId || undefined, inReplyTo: composeInReplyTo || undefined, references: composeReferences || undefined, draftKey: effectiveComposeDraftStorageKey || undefined, providerAccountId: providerAccounts.find((entry) => entry.accountEmail === composeAccountEmail.toLowerCase())?.id || undefined, + attachments: composeAttachments.map((a) => ({ + filename: a.filename, + contentType: a.contentType, + contentBase64: a.contentBase64, + })), }), }); const payload = await response.json().catch(() => ({})); @@ -2056,8 +2161,10 @@ ${sourceText}`; setShowBcc(false); setComposeSubject(""); setComposeBody(""); + setComposeBodyHtml(""); + setComposeAttachments([]); setIsComposeOpen(false); - setComposeSuccess("Email sent."); + showSentToast("Message Sent"); if (activeModule === "sent" || activeModule === "drafts") { await Promise.all([loadMessages(), loadMailboxCounts(), loadUnreadBadges()]); } else { @@ -2070,6 +2177,96 @@ ${sourceText}`; } }; + const composeHasMeaningfulBody = useMemo(() => { + const stripped = composeBodyHtml.replace(/<[^>]+>/g, "").replace(/ /gi, " ").trim(); + return Boolean(composeBody.trim() || stripped); + }, [composeBody, composeBodyHtml]); + + const addComposeAttachmentsFromFiles = useCallback(async (fileList: FileList | null) => { + if (!fileList?.length) return; + const additions: Array<{ id: string; filename: string; contentType: string; contentBase64: string }> = []; + for (let i = 0; i < fileList.length; i++) { + const file = fileList.item(i); + if (!file) continue; + const dataUrl = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(String(reader.result)); + reader.onerror = () => reject(reader.error); + reader.readAsDataURL(file); + }); + const comma = dataUrl.indexOf(","); + const contentBase64 = comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl; + additions.push({ + id: `${Date.now()}-${i}-${file.name}`, + filename: file.name, + contentType: file.type || "application/octet-stream", + contentBase64, + }); + } + setComposeAttachments((prev) => [...prev, ...additions]); + }, []); + + const handleSaveComposeTemplate = async () => { + const name = saveTemplateName.trim(); + if (!name || !composeAccountEmail || saveTemplateSaving) return; + setSaveTemplateSaving(true); + setComposeError(""); + try { + const response = await fetch("/api/gmail/templates", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + accountEmail: composeAccountEmail, + name, + subject: composeSubject.trim() || "", + bodyHtml: composeBodyHtml, + bodyText: composeBody, + }), + }); + const payload = await response.json().catch(() => ({})); + if (!response.ok) throw new Error(payload?.message || "Failed to save template."); + setSaveTemplateModalOpen(false); + setSaveTemplateName(""); + const listRes = await fetch( + `/api/gmail/templates?accountEmail=${encodeURIComponent(composeAccountEmail)}` + ); + const listPayload = await listRes.json().catch(() => ({})); + setComposeTemplates(Array.isArray(listPayload?.templates) ? listPayload.templates : []); + } catch (error) { + setComposeError(error instanceof Error ? error.message : "Failed to save template."); + } finally { + setSaveTemplateSaving(false); + } + }; + + const applyComposeTemplate = (templateId: string) => { + const template = composeTemplates.find((entry) => entry.id === templateId); + if (!template) return; + if (template.subject) setComposeSubject(template.subject); + if (template.bodyHtml && template.bodyHtml.trim()) { + setComposeBodyHtml(template.bodyHtml); + setComposeBody(template.bodyText || ""); + } else if (template.bodyText && template.bodyText.trim()) { + const raw = template.bodyText; + setComposeBody(raw); + const esc = (value: string) => + value.replace(/&/g, "&").replace(//g, ">"); + setComposeBodyHtml(`

${esc(raw).replace(/\n/g, "
")}

`); + } + setComposeTemplateMenuOpen(false); + }; + + const handlePrintCompose = () => { + const html = + composeBodyHtml.trim() || + `

${composeBody + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\n/g, "
")}

`; + printComposeContent(composeSubject || "(No Subject)", html); + }; + const messagesCountLabel = `${filteredMessages.length} Messages`; const activeModuleLabel = MODULES.find((item) => item.key === activeModule)?.label || "Mailbox"; const pageLabel = `Page ${currentPage}`; @@ -2757,7 +2954,7 @@ ${sourceText}`; setDetailError(""); }} className={`w-full text-left px-3 py-2.5 flex items-center gap-2 transition hover:bg-[#F4EDFF] ${ - isSelected ? "bg-[#EDE1FF]" : message.unread ? "font-semibold bg-[#FBFBFF]" : "" + isSelected ? "bg-[#EDE1FF]" : message.unread ? "bg-[#FBFBFF]" : "" }`} > ) : null} - + {message.isComposeDraft ? ( <> (Draft) @@ -2786,11 +2987,20 @@ ${sourceText}`; senderOrTo )} - - {message.subject || "(No Subject)"} - - {message.snippet || "No preview available."} + + + {message.subject || "(No Subject)"} + + + {" "} + - {message.snippet || "No preview available."} + - + {formatDate(message.timestamp, message.date)} @@ -2959,76 +3169,105 @@ ${sourceText}`; ))} {inlineComposeMode ? ( -
-
-

- {inlineComposeMode === "forward" - ? "Forward Message" - : inlineComposeMode === "replyAll" - ? "Reply All" - : "Reply"} -

-
-
-
- - {inlineComposeMode === "forward" ? "To" : "Replying To"} +
+
+
+
+
+ +
+
+

+ {inlineComposeMode === "forward" + ? "Forward" + : inlineComposeMode === "replyAll" + ? "Reply all" + : "Reply"} +

+

Compose below the thread

+
+
+ + Draft - setInlineComposeTo(event.target.value)} - className="w-full bg-transparent px-0 py-1.5 text-sm text-[#111827] outline-none" - placeholder="Recipient Email" - />
-
- Subject - setInlineComposeSubject(event.target.value)} - className="w-full bg-transparent px-0 py-1.5 text-sm text-[#111827] outline-none" - /> -
-
-