Close the composer after a message is sent (#83)
After a successful send the composer kept its window open, emptied, and the "Message sent" toast lived inside it. The popup now closes (the full-page composer goes to Sent) and the confirmation is shown by FloatingComposer through the compose context, so it stays visible once the composer is gone. It is a status region so screen readers announce it. Sending also cancels the pending autosave and invalidates in-flight ones. Before, a save that fired during the send could recreate the message as a draft, which then sat in Drafts next to the sent copy. Co-authored-by: Can Kirca <9063990+cankirca@users.noreply.github.com>
This commit is contained in:
1 parent
21817cedcb
commit
fed60c1b18
3 files changed
+37
-8
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useState } from "react";
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type ComposeContextValue = {
|
||||
@@ -9,6 +9,8 @@ type ComposeContextValue = {
|
||||
openComposer: () => void;
|
||||
openDraftComposer: (draftId: string) => void;
|
||||
closeComposer: () => void;
|
||||
notice: string | null;
|
||||
showNotice: (message: string) => void;
|
||||
};
|
||||
|
||||
const ComposeContext = createContext<ComposeContextValue | null>(null);
|
||||
@@ -22,6 +24,13 @@ export function useCompose() {
|
||||
export function ComposeProvider({ children }: { children: ReactNode }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [draftId, setDraftId] = useState<string | null>(null);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!notice) return;
|
||||
const timer = setTimeout(() => setNotice(null), 3200);
|
||||
return () => clearTimeout(timer);
|
||||
}, [notice]);
|
||||
|
||||
return (
|
||||
<ComposeContext.Provider
|
||||
@@ -40,6 +49,8 @@ export function ComposeProvider({ children }: { children: ReactNode }) {
|
||||
setOpen(false);
|
||||
setDraftId(null);
|
||||
},
|
||||
notice,
|
||||
showNotice: setNotice,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Tooltip } from "@/components/ui/tooltip";
|
||||
import { useSelectedMailbox } from "@/components/mailbox-provider";
|
||||
import { authFetch } from "@/lib/auth/client";
|
||||
import { formatEmailAddress, getEmailAddress } from "@/lib/email/address";
|
||||
import { useCompose } from "@/components/compose/compose-context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SendReview } from "@/components/agent/send-review";
|
||||
import type { ReviewSnapshot } from "@/components/agent/send-review-types";
|
||||
@@ -45,6 +46,7 @@ export function ComposeForm({
|
||||
}) {
|
||||
const { t } = useLanguage();
|
||||
const router = useRouter();
|
||||
const { showNotice } = useCompose();
|
||||
const { selectedMailbox, setSelectedMailbox, mailboxes } = useSelectedMailbox();
|
||||
const [draftId, setDraftId] = useState<string | null>(null);
|
||||
const [agentRevision, setAgentRevision] = useState<number | null>(null);
|
||||
@@ -253,6 +255,7 @@ export function ComposeForm({
|
||||
setToast({ type: "error", message: t("compose.error.emptyBody") });
|
||||
return;
|
||||
}
|
||||
if (saveTimer.current) clearTimeout(saveTimer.current);
|
||||
setLoading(true);
|
||||
const fullHtml = joinQuotedHtml(html, quotedHtml);
|
||||
if (draftId && agentRevision !== null) {
|
||||
@@ -310,6 +313,7 @@ export function ComposeForm({
|
||||
window.dispatchEvent(new Event("mailflare:messages-changed"));
|
||||
});
|
||||
}
|
||||
draftGeneration.current += 1;
|
||||
setDraftId(null);
|
||||
setTo([]);
|
||||
setCc([]);
|
||||
@@ -323,8 +327,14 @@ export function ComposeForm({
|
||||
setQuotedHtml(null);
|
||||
setAttachments([]);
|
||||
setScheduledAt(null);
|
||||
setToast({ type: "success", message: data.scheduled ? t("compose.scheduled") : t("compose.sent") });
|
||||
showNotice(data.scheduled ? t("compose.scheduled") : t("compose.sent"));
|
||||
window.dispatchEvent(new Event("mailflare:messages-changed"));
|
||||
|
||||
if (onClose) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
router.push("/sent");
|
||||
}
|
||||
|
||||
async function deleteDraftAndClose() {
|
||||
|
||||
@@ -5,12 +5,20 @@ import { ComposeForm } from "@/components/compose/compose-form";
|
||||
import { useCompose } from "@/components/compose/compose-context";
|
||||
|
||||
export function FloatingComposer() {
|
||||
const { open, draftId, closeComposer } = useCompose();
|
||||
const { open, draftId, closeComposer, notice } = useCompose();
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
if (!open) return null;
|
||||
return <ComposeForm key={draftId ?? "new"} mode="popup" draftIdToLoad={draftId} onClose={() => {
|
||||
closeComposer();
|
||||
if (/^\/drafts\/[^/]+\/?$/.test(pathname)) router.replace("/drafts");
|
||||
}} />;
|
||||
return (
|
||||
<>
|
||||
{notice && (
|
||||
<div role="status" className="fixed right-6 top-6 z-[60] rounded-lg bg-green-600 px-4 py-3 text-sm font-medium text-white shadow-lg">
|
||||
{notice}
|
||||
</div>
|
||||
)}
|
||||
{open && <ComposeForm key={draftId ?? "new"} mode="popup" draftIdToLoad={draftId} onClose={() => {
|
||||
closeComposer();
|
||||
if (/^\/drafts\/[^/]+\/?$/.test(pathname)) router.replace("/drafts");
|
||||
}} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user