ultisuite-client/components/gmail/calendar-invitation-preview.tsx
R3D347HR4Y efaaf16f60
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: update metadata and layout for new product pages
- Refactored metadata for contacts, administration, and Ulticards pages to utilize dynamic app names and descriptions.
- Introduced new product pages for Ultiai, Ultical, Ulticards, Ultidrive, Ultimail, and Ultimeet with appropriate metadata.
- Enhanced layout components to ensure consistent styling and functionality across new product sections.
- Updated various components to replace hardcoded labels with dynamic references to improve maintainability and consistency.
2026-06-19 22:11:42 +02:00

162 lines
5.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useMemo, useState } from "react"
import { format } from "date-fns"
import { InvitationTimeChipText } from "@/components/gmail/invitation-time-chip-text"
import { AgendaMark } from "@/components/suite/agenda-mark"
import { Icon } from "@iconify/react"
import { ThumbsDown, ThumbsUp, Users, MoreVertical } from "lucide-react"
import {
VIDEO_CONFERENCE_LOGOS,
formatInvitationAttendeeLine,
type ParsedCalendarInvitation,
} from "@/lib/calendar-invitation"
import { ensureVcLogosCollection } from "@/lib/register-vc-logos"
import { cn } from "@/lib/utils"
import { MAIL_INVITATION_CARD_CLASS } from "@/lib/mail-chrome-classes"
function attendeeDisplayList(inv: ParsedCalendarInvitation): {
organizerLine?: string
othersLine?: string
} {
const orgEmail = inv.organizer?.email
const organizerLine =
orgEmail || inv.organizer?.name
? `${orgEmail ?? inv.organizer?.name} Organisateur`
: undefined
const others = inv.attendees.filter((a) => {
const e = a.email?.toLowerCase()
return e && e !== orgEmail?.toLowerCase()
})
const line = formatInvitationAttendeeLine(
others.length > 0 ? others : inv.attendees,
4
)
return {
organizerLine,
othersLine: line || undefined,
}
}
const RSVP_BTN =
"rounded-full bg-[#1a73e8] px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-[#1557b0]"
const RSVP_SECONDARY =
"rounded-full border border-border bg-mail-surface px-4 py-2 text-sm font-medium text-primary transition-colors hover:bg-accent"
export function CalendarInvitationPreview({
invitation,
className,
calendarAppName = "Agenda",
}: {
invitation: ParsedCalendarInvitation
className?: string
/** Nom de l'app calendrier affiché dans le bouton « Ouvrir dans … ». */
calendarAppName?: string
}) {
ensureVcLogosCollection()
const { organizerLine, othersLine } = useMemo(
() => attendeeDisplayList(invitation),
[invitation]
)
const confIcon = VIDEO_CONFERENCE_LOGOS[invitation.conferenceProvider]
const [rsvp, setRsvp] = useState<string | null>(null)
return (
<div
className={cn(
MAIL_INVITATION_CARD_CLASS,
className
)}
>
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between md:gap-4">
<div className="min-w-0 flex-1 space-y-2">
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
<Icon icon={confIcon} className="size-5 shrink-0" aria-hidden />
<InvitationTimeChipText
start={invitation.start}
end={invitation.end}
/>
</div>
<h2 className="text-xl font-normal leading-snug text-foreground">
{invitation.summary}
</h2>
{organizerLine && (
<p className="text-sm text-foreground/90">{organizerLine}</p>
)}
{othersLine && (
<p className="flex flex-wrap items-start gap-1.5 text-sm text-foreground/90">
<Users className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden />
<span>{othersLine}</span>
</p>
)}
</div>
<div className="flex shrink-0 flex-row items-start gap-3 md:flex-col md:items-end">
<div className="flex size-12 shrink-0 items-center justify-center rounded-lg border border-border bg-mail-surface shadow-sm">
<AgendaMark className="size-9 object-contain" />
</div>
<div className="min-w-0 text-right text-sm leading-snug text-muted-foreground">
<p className="font-medium text-foreground">Dans votre agenda</p>
<p className="mt-0.5">Aucun autre événement à cette date</p>
</div>
</div>
</div>
<div className="mt-4 flex flex-wrap items-center gap-2">
{(["Oui", "Non", "Peut-être"] as const).map((label) => (
<button
key={label}
type="button"
className={cn(RSVP_BTN, rsvp === label && "ring-2 ring-[#202124]/30")}
onClick={() => setRsvp(label)}
>
{label}
</button>
))}
<button type="button" className={RSVP_SECONDARY}>
Proposer un autre horaire
</button>
<a
href={`/agenda/day/${format(invitation.start, "yyyy-MM-dd")}`}
className={RSVP_SECONDARY}
>
Ouvrir dans {calendarAppName}
</a>
<button
type="button"
className="ml-auto flex size-10 items-center justify-center rounded-full border border-border bg-mail-surface text-muted-foreground hover:bg-accent md:ml-0"
aria-label="Plus doptions"
>
<MoreVertical className="size-[18px]" strokeWidth={1.5} />
</button>
</div>
<div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t border-border/60 pt-3 text-xs text-muted-foreground">
<span>Daprès cet e-mail</span>
<div className="flex items-center gap-2">
<span>Correct ?</span>
<button
type="button"
className="rounded p-1 text-[#5f6368] hover:bg-black/5"
aria-label="Oui, correct"
>
<ThumbsUp className="size-4" strokeWidth={1.5} />
</button>
<button
type="button"
className="rounded p-1 text-[#5f6368] hover:bg-black/5"
aria-label="Non, incorrect"
>
<ThumbsDown className="size-4" strokeWidth={1.5} />
</button>
</div>
</div>
</div>
)
}