ultisuite-client/components/gmail/calendar-invitation-preview.tsx
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- Introduced turbopack alias for canvas in next.config.mjs.
- Updated package.json scripts for development and branding tasks.
- Added new dependencies for Tiptap extensions.
- Implemented new demo layouts for agenda, contacts, drive, and mail applications.
- Enhanced globals.css for improved theming and splash screen animations.
- Added OAuth callback handling for drive mounts.
- Updated layout components to integrate new demo shells and improve structure.
2026-06-12 19:10:24 +02:00

159 lines
5.4 KiB
TypeScript
Raw 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,
}: {
invitation: ParsedCalendarInvitation
className?: 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 Agenda
</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>
)
}