From ef085ca03c026f9a7897a59110eacef61a72786f Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Fri, 24 Jul 2026 09:42:20 -0700 Subject: [PATCH] Phase 2 of UX overhaul --- src/app/(inbox)/calendar/page.tsx | 31 +++-- src/app/(inbox)/settings/SignatureForm.tsx | 143 +++++++++++++++++---- src/app/(inbox)/settings/page.tsx | 45 ++++--- src/app/layout.tsx | 36 +++--- src/app/login/page.tsx | 33 ++++- src/components/AttachmentList.tsx | 25 ++-- src/components/CalendarEventCard.tsx | 21 ++- src/components/EmailDetailView.tsx | 43 ++++--- src/components/LoadingSkeletons.tsx | 49 ++++--- src/components/MarkUnreadButton.tsx | 20 ++- src/components/MobileNav.tsx | 22 ++-- src/components/NotSpamButton.tsx | 14 +- src/components/PinButton.tsx | 11 ++ src/components/Popover.tsx | 108 ++++++++++++++++ src/components/SpreadsheetViewer.tsx | 28 ++-- src/components/ThreadView.tsx | 29 +++-- src/components/ToastProvider.tsx | 23 ++++ src/components/useModalDialog.ts | 73 +++++++++++ src/lib/__tests__/compose.test.ts | 14 ++ src/lib/compose.ts | 5 + 20 files changed, 594 insertions(+), 179 deletions(-) create mode 100644 src/components/Popover.tsx create mode 100644 src/components/useModalDialog.ts diff --git a/src/app/(inbox)/calendar/page.tsx b/src/app/(inbox)/calendar/page.tsx index e0835f6..8cbcefe 100644 --- a/src/app/(inbox)/calendar/page.tsx +++ b/src/app/(inbox)/calendar/page.tsx @@ -67,36 +67,43 @@ export default async function CalendarPage({ searchParams }: Props) {

-
+
{monthKey !== currentMonthKey && ( Today )} - Prev + + Previous -
+

{monthTitle(monthKey)} -

+ - Next + Next +
-
+
{DAY_LABELS.map((label) => (
- {day.date.getDate()} - +
{day.events.map((event) => ( diff --git a/src/app/(inbox)/settings/SignatureForm.tsx b/src/app/(inbox)/settings/SignatureForm.tsx index e96cbfe..470b2e8 100644 --- a/src/app/(inbox)/settings/SignatureForm.tsx +++ b/src/app/(inbox)/settings/SignatureForm.tsx @@ -1,8 +1,12 @@ "use client"; -import { useState, useTransition } from "react"; +import { useEffect, useState, useTransition } from "react"; import { saveSignatureAction } from "./actions"; -import { stripSignatureSeparator } from "@/lib/compose"; +import { + formatSignatureForSave, + stripSignatureSeparator, +} from "@/lib/compose"; +import { useToast } from "@/components/ToastProvider"; interface Props { identityLabel?: string; @@ -16,53 +20,144 @@ function stripSepPrefix(sig: string): string { } export default function SignatureForm({ identityLabel, initialSignature }: Props) { - const [value, setValue] = useState(() => stripSepPrefix(initialSignature)); - const [saved, setSaved] = useState(false); + const initialValue = stripSepPrefix(initialSignature); + const [value, setValue] = useState(initialValue); + const [savedValue, setSavedValue] = useState(initialValue); const [error, setError] = useState(null); const [isPending, startTransition] = useTransition(); + const showToast = useToast(); + const dirty = value !== savedValue; + + useEffect(() => { + if (!dirty) return; + function warnBeforeUnload(event: BeforeUnloadEvent) { + event.preventDefault(); + } + window.addEventListener("beforeunload", warnBeforeUnload); + return () => window.removeEventListener("beforeunload", warnBeforeUnload); + }, [dirty]); function handleSave() { + if (!dirty || isPending) return; setError(null); - setSaved(false); startTransition(async () => { try { // Re-add the `-- \n` prefix so Fastmail stores it in standard format, // which other mail clients (Fastmail web, mobile apps) also expect. - const toSave = value.trim() ? `-- \n${value.trim()}` : ""; + const normalized = value.trim(); + const toSave = formatSignatureForSave(value); await saveSignatureAction(toSave); - setSaved(true); + setValue(normalized); + setSavedValue(normalized); + showToast({ message: "Signature saved" }); } catch (e) { - setError(e instanceof Error ? e.message : "Failed to save"); + const message = e instanceof Error ? e.message : "Failed to save"; + setError(message); + showToast({ message: "Couldn’t save the signature.", tone: "error" }); } }); } return ( -
+
{identityLabel && ( -

{identityLabel}

+
+

+ Sending as +

+

+ {identityLabel} +

+
)} -