ultisuite-client/components/gmail/email-view/email-view-messages.tsx
R3D347HR4Y c87670e90f
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
feat(api): offline-first mail sync w/ TanStack Query
Move mail, compose, contacts, and accounts off mocks onto REST + WS.
Add client, auth store, IDB-backed query cache, offline queue, and
sync bar; hybrid Zustand for UI-only state. Settings still local until
backend has preferences API.
2026-05-23 00:04:28 +02:00

173 lines
5.3 KiB
TypeScript

"use client"
import { Star, Info } from "lucide-react"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import {
avatarColor,
cleanSenderName,
senderInitial,
} from "@/lib/sender-display"
import { MailDateText } from "@/components/gmail/mail-date-text"
import type { ApiMessageFull } from "@/lib/api/types"
import type { EmailAttachment } from "@/lib/email-data"
import { ContactHoverCard } from "@/components/gmail/contact-hover-card"
import { EmailViewMessageToolbar } from "@/components/gmail/email-view/email-view-toolbar"
import { SandboxedContent } from "@/components/gmail/email-view/sandboxed-content"
import { MessageAttachmentsSection } from "@/components/gmail/email-view/message-attachments"
import {
MAIL_MESSAGE_HOVER_CLASS,
MAIL_TOOLTIP_CONTENT_CLASS,
} from "@/lib/mail-chrome-classes"
export function CollapsedMessage({
message,
onClick,
}: {
message: ApiMessageFull
onClick: () => void
}) {
const senderName = message.from[0]?.name ?? ""
const senderAddr = message.from[0]?.address ?? ""
const name = cleanSenderName(senderName)
const color = avatarColor(name)
return (
<div
role="button"
tabIndex={0}
onClick={onClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
onClick()
}
}}
className={cn("group flex w-full cursor-pointer items-center gap-3 px-4 py-3 text-left transition-colors", MAIL_MESSAGE_HOVER_CLASS)}
>
<div
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-sm font-bold text-white"
style={{ backgroundColor: color }}
>
{senderInitial(name)}
</div>
<div className="min-w-0 flex-1 flex flex-col gap-1" data-selectable-text>
<div className="flex min-w-0 items-center justify-between gap-2">
<ContactHoverCard displayName={senderName} email={senderAddr} className="min-w-0">
<span className="truncate text-sm font-semibold text-foreground">{name}</span>
</ContactHoverCard>
<div className="flex shrink-0 items-center gap-1">
<MailDateText
iso={message.date}
variant="preview"
className="text-xs text-muted-foreground"
/>
<Star
strokeWidth={1.25}
className="ml-1 size-4 fill-transparent stroke-[#c2c2c2]"
/>
</div>
</div>
<p className="min-w-0 truncate text-sm leading-snug text-muted-foreground">{message.snippet}</p>
</div>
</div>
)
}
export function ExpandedMessage({
sender,
senderEmail,
dateIso,
body,
isSpam,
isLast,
starred,
attachments = [],
onToggleStar,
onCollapse,
onPrintConversation,
}: {
sender: string
senderEmail: string
dateIso: string
body: string
isSpam: boolean
isLast: boolean
starred: boolean
attachments?: EmailAttachment[]
onToggleStar?: () => void
onCollapse?: () => void
onPrintConversation?: () => void
}) {
return (
<div>
<EmailViewMessageToolbar
sender={sender}
senderEmail={senderEmail}
dateIso={dateIso}
isSpam={isSpam}
isLast={isLast}
starred={starred}
onToggleStar={onToggleStar}
onCollapse={onCollapse}
onPrintConversation={onPrintConversation}
/>
<div
className={cn(
"px-4 pl-[68px] max-sm:pl-4 max-sm:pr-4",
attachments.length > 0 ? "pb-0" : "pb-4"
)}
data-selectable-text
>
<SandboxedContent html={body} isSpam={isSpam} />
</div>
{attachments.length > 0 && (
<MessageAttachmentsSection attachments={attachments} />
)}
</div>
)
}
export function SpamWhyBanner({ onNotSpam }: { onNotSpam?: () => void }) {
return (
<div className="mx-6 mb-4 flex items-start gap-3 rounded-lg border border-border bg-muted px-4 py-3.5 max-sm:mx-4">
<div className="min-w-0 flex-1 space-y-3">
<p className="text-sm leading-snug text-foreground/80">
<span className="font-medium text-foreground">Pourquoi ce message est-il dans le spam ?</span>{" "}
Ce message est semblable à des messages identifiés comme spam par le passé.
</p>
{onNotSpam && (
<button
type="button"
onClick={onNotSpam}
className="rounded-md border border-border bg-mail-surface px-4 py-2 text-sm font-medium text-primary shadow-sm transition-colors hover:bg-accent"
>
Signaler comme non-spam
</button>
)}
</div>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-accent"
aria-label="En savoir plus sur le filtre anti-spam"
>
<Info className="h-[18px] w-[18px]" strokeWidth={1.75} />
</button>
</TooltipTrigger>
<TooltipContent side="left" className={cn(MAIL_TOOLTIP_CONTENT_CLASS, "max-w-xs text-xs")}>
Les filtres peuvent se tromper. Si le message est légitime, signalez-le comme non-spam pour
l&apos;améliorer.
</TooltipContent>
</Tooltip>
</div>
)
}