ultisuite-client/lib/pending-send-toast.tsx
R3D347HR4Y 9266aa34cd huhu
2026-05-19 22:20:43 +02:00

180 lines
5.3 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
import { Ban, Loader2, Send } from "lucide-react"
import { toast } from "sonner"
import { MAIL_TOAST_SURFACE_CLASS } from "@/lib/mail-chrome-classes"
import { cn } from "@/lib/utils"
const DEFAULT_DURATION_MS = 3000
type RefBox<T> = { current: T }
export function showPendingSendToast(options: {
durationMs?: number
onCommit: () => void | Promise<void>
onCancel: () => void
}) {
const durationMs = options.durationMs ?? DEFAULT_DURATION_MS
const committedRef: RefBox<boolean> = { current: false }
const timerRef: RefBox<number | null> = {
current: null,
}
toast.custom(
(toastId) => (
<PendingSendToastBody
toastId={toastId}
durationMs={durationMs}
committedRef={committedRef}
timerRef={timerRef}
onCommit={options.onCommit}
onCancel={options.onCancel}
/>
),
{
duration: Infinity,
onDismiss: () => {
if (timerRef.current != null) {
clearTimeout(timerRef.current)
timerRef.current = null
}
if (!committedRef.current) {
committedRef.current = true
options.onCancel()
}
},
}
)
}
function PendingSendToastBody({
toastId,
durationMs,
committedRef,
timerRef,
onCommit,
onCancel,
}: {
toastId: string | number
durationMs: number
committedRef: RefBox<boolean>
timerRef: RefBox<number | null>
onCommit: () => void | Promise<void>
onCancel: () => void
}) {
const onCommitRef = useRef(onCommit)
const onCancelRef = useRef(onCancel)
onCommitRef.current = onCommit
onCancelRef.current = onCancel
const [barImmediate, setBarImmediate] = useState(false)
const commitNowRef = useRef<(opts?: { manual?: boolean }) => void>(() => {})
commitNowRef.current = (opts?: { manual?: boolean }) => {
if (committedRef.current) return
if (opts?.manual) setBarImmediate(true)
committedRef.current = true
if (timerRef.current != null) {
clearTimeout(timerRef.current)
timerRef.current = null
}
void (async () => {
try {
await onCommitRef.current()
toast.success("Message envoyé")
} catch {
toast.error("L'envoi a échoué")
onCancelRef.current()
} finally {
toast.dismiss(toastId)
}
})()
}
useEffect(() => {
timerRef.current = window.setTimeout(() => {
commitNowRef.current()
}, durationMs)
return () => {
if (timerRef.current != null) {
clearTimeout(timerRef.current)
timerRef.current = null
}
}
}, [durationMs, timerRef])
const handleSendNow = () => {
commitNowRef.current({ manual: true })
}
const handleCancel = () => {
if (committedRef.current) return
committedRef.current = true
if (timerRef.current != null) {
clearTimeout(timerRef.current)
timerRef.current = null
}
onCancelRef.current()
toast.dismiss(toastId)
}
return (
<div className={cn(MAIL_TOAST_SURFACE_CLASS, "backdrop-blur-sm")}>
<div className="px-3.5 pb-2.5 pt-3">
<div className="flex items-center gap-3">
<span
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-primary/15 text-primary"
aria-hidden
>
<Loader2 className="h-4 w-4 animate-spin" strokeWidth={2} />
</span>
<div className="min-w-0">
<p className="text-[13px] font-semibold leading-snug tracking-tight text-foreground">
Envoi en cours
</p>
</div>
</div>
<div className="mt-2.5 grid grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)] gap-2">
<button
type="button"
onClick={handleSendNow}
className="inline-flex min-h-9 min-w-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-lg bg-[#1a73e8] px-2.5 py-2 text-xs font-semibold leading-tight text-white shadow-sm transition-colors hover:bg-[#1765cc] active:bg-[#1557b0]"
>
<Send className="h-3.5 w-3.5 shrink-0 opacity-95" strokeWidth={2} aria-hidden />
<span>Envoyer maintenant</span>
</button>
<button
type="button"
onClick={handleCancel}
className="inline-flex min-h-9 min-w-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-lg border border-border bg-mail-surface px-2.5 py-2 text-xs font-semibold leading-tight text-muted-foreground shadow-sm transition-colors hover:border-border hover:bg-accent hover:text-foreground"
>
<Ban className="h-3.5 w-3.5 shrink-0" strokeWidth={2} aria-hidden />
<span>Annuler l&apos;envoi</span>
</button>
</div>
</div>
<div className="relative h-1 w-full shrink-0 bg-muted">
<div
className="absolute inset-y-0 left-0 bg-linear-to-r from-[#1a73e8] to-[#4285f4]"
style={
barImmediate
? { width: "100%" }
: {
width: "0%",
animation: `ultimail-pending-send-progress ${durationMs}ms linear forwards`,
}
}
/>
</div>
<style>{`
@keyframes ultimail-pending-send-progress {
from { width: 0%; }
to { width: 100%; }
}
`}</style>
</div>
)
}