import { useEffect, useState } from "react"; import { Calendar, Check, HelpCircle, MapPin, X } from "lucide-react"; import { useLocation } from "wouter"; import type { CalendarEvent, Email, EmailBodyPart } from "@/jmap/types"; import { useCalendar, toInstance, myParticipantKeys } from "@/store/calendar"; import { formatTimeRange } from "@/lib/dates"; import { toast } from "@/ui/toast"; export function InviteCard({ email, part }: { email: Email; part: EmailBodyPart }) { const cal = useCalendar(); const [, navigate] = useLocation(); const [events, setEvents] = useState(null); const [existing, setExisting] = useState(null); const [busy, setBusy] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (!cal.available || !part.blobId) return; let cancelled = false; cal .parseIcs(part.blobId) .then(async (evs) => { if (cancelled) return; setEvents(evs); const first = evs[0]; if (first?.uid) setExisting(await cal.findByUid(first.uid)); }) .catch((err) => !cancelled && setError((err as Error).message)); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [part.blobId, cal.available]); if (!cal.available) return null; if (error) return null; const ev = events?.[0]; if (!ev) return null; const method = (ev.method ?? "").toUpperCase(); const inst = toInstance({ ...ev, id: "tmp", calendarIds: {} } as CalendarEvent, cal.calendars); const organizer = Object.values(ev.participants ?? {}).find((p) => p.roles?.owner); const location = Object.values(ev.locations ?? {})[0]?.name; const myStatus = existing ? (myParticipantKeys(existing, cal.identities).map((k) => existing.participants?.[k]?.participationStatus)[0] ?? null) : null; const attendees = Object.values(ev.participants ?? {}).filter((p) => p.roles?.attendee); const respond = async (status: "accepted" | "tentative" | "declined") => { setBusy(status); try { let target = existing; if (!target) { const calId = Object.values(cal.calendars).find((c) => c.isDefault)?.id ?? Object.keys(cal.calendars)[0]; if (!calId) throw new Error("No calendar available"); const id = await cal.importEvent(ev, calId); target = await cal.getEvent(id); } if (!target) throw new Error("Could not add the event to your calendar"); await cal.rsvp(target.id, status); setExisting(await cal.getEvent(target.id)); toast.success(status === "accepted" ? "Invitation accepted" : status === "declined" ? "Invitation declined" : "Marked as tentative"); } catch (err) { toast.error((err as Error).message); } finally { setBusy(null); } }; const addToCalendar = async () => { setBusy("add"); try { const calId = Object.values(cal.calendars).find((c) => c.isDefault)?.id ?? Object.keys(cal.calendars)[0]; if (!calId) throw new Error("No calendar available"); const id = await cal.importEvent(ev, calId); setExisting(await cal.getEvent(id)); toast.success("Added to your calendar"); } catch (err) { toast.error((err as Error).message); } finally { setBusy(null); } }; const title = method === "CANCEL" ? "Cancelled event" : method === "REPLY" ? "Invitation reply" : method === "REQUEST" ? (existing ? "Invitation (in your calendar)" : "Invitation") : "Event"; return (
{title}{method === "REPLY" && organizer ? "" : ""}

{ev.title || "(untitled event)"}

{inst &&
{formatTimeRange(inst.start, inst.end, inst.allDay)}{ev.timeZone ? ` (${ev.timeZone})` : ""}
} {location &&
{location}
} {organizer &&
Organizer: {organizer.name || organizer.email || Object.values(organizer.sendTo ?? {})[0]?.replace("mailto:", "")}
} {attendees.length > 0 &&
{attendees.length} attendee{attendees.length === 1 ? "" : "s"}
} {method === "REPLY" && (
{attendees.map((a) =>
{a.name || a.email}: {a.participationStatus ?? "unknown"}
)}
)}
{method !== "REPLY" && method !== "CANCEL" && (
{(method === "REQUEST" || attendees.length > 0) ? ( <> ) : ( !existing && )} {existing && inst && }
)} {method === "CANCEL" && existing && (
)} {email.id}
); }