rspace-online/modules/rschedule/lib/calendar-links.ts

103 lines
3.6 KiB
TypeScript

/**
* Calendar-link helpers — port of schedule-jeffemmett/src/lib/calendar-links.ts.
*
* Used in confirmation emails so guests can add the booking to their calendar
* with one click, and in `.ics` attachments for Apple Calendar / Outlook.
*/
function formatUtcDate(iso: string): string {
return new Date(iso).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
}
export interface CalLinkParams {
startUtc: string;
endUtc: string;
title: string;
meetingLink?: string | null;
guestNote?: string | null;
locationOverride?: string | null;
}
export function getGoogleCalendarUrl(p: CalLinkParams): string {
const start = formatUtcDate(p.startUtc);
const end = formatUtcDate(p.endUtc);
const details = [p.guestNote ? `Note: ${p.guestNote}` : "", p.meetingLink ? `Join: ${p.meetingLink}` : ""].filter(Boolean).join("\n");
const q = new URLSearchParams({
action: "TEMPLATE",
text: p.title,
dates: `${start}/${end}`,
...(details && { details }),
...(p.meetingLink ? { location: p.meetingLink } : p.locationOverride ? { location: p.locationOverride } : {}),
});
return `https://calendar.google.com/calendar/render?${q.toString()}`;
}
export function getOutlookCalendarUrl(p: CalLinkParams): string {
const body = [p.guestNote ? `Note: ${p.guestNote}` : "", p.meetingLink ? `Join: ${p.meetingLink}` : ""].filter(Boolean).join("\n");
const q = new URLSearchParams({
path: "/calendar/action/compose",
rru: "addevent",
subject: p.title,
startdt: new Date(p.startUtc).toISOString(),
enddt: new Date(p.endUtc).toISOString(),
...(body && { body }),
...(p.meetingLink ? { location: p.meetingLink } : {}),
});
return `https://outlook.live.com/calendar/0/action/compose?${q.toString()}`;
}
export function getYahooCalendarUrl(p: CalLinkParams): string {
const start = formatUtcDate(p.startUtc);
const end = formatUtcDate(p.endUtc);
const desc = [p.guestNote ? `Note: ${p.guestNote}` : "", p.meetingLink ? `Join: ${p.meetingLink}` : ""].filter(Boolean).join("\n");
const q = new URLSearchParams({
v: "60",
title: p.title,
st: start,
et: end,
...(desc && { desc }),
...(p.meetingLink ? { in_loc: p.meetingLink } : {}),
});
return `https://calendar.yahoo.com/?${q.toString()}`;
}
export interface IcsParams extends CalLinkParams {
attendees?: string[];
organizerEmail: string;
organizerName: string;
uidDomain?: string;
method?: "REQUEST" | "PUBLISH" | "CANCEL";
}
export function generateIcsContent(p: IcsParams): string {
const start = formatUtcDate(p.startUtc);
const end = formatUtcDate(p.endUtc);
const uid = `${Date.now()}-${Math.random().toString(36).slice(2)}@${p.uidDomain || "rschedule.rspace.online"}`;
const stamp = formatUtcDate(new Date().toISOString());
const description = [p.guestNote ? `Note: ${p.guestNote}` : "", p.meetingLink ? `Join: ${p.meetingLink}` : ""].filter(Boolean).join("\\n");
const method = p.method ?? (p.attendees?.length ? "REQUEST" : "PUBLISH");
const lines = [
"BEGIN:VCALENDAR",
"VERSION:2.0",
"PRODID:-//rSchedule//Booking//EN",
"CALSCALE:GREGORIAN",
`METHOD:${method}`,
"BEGIN:VEVENT",
`UID:${uid}`,
`DTSTAMP:${stamp}`,
`DTSTART:${start}`,
`DTEND:${end}`,
`SUMMARY:${p.title.replace(/([;,\\\n])/g, "\\$1")}`,
...(description ? [`DESCRIPTION:${description}`] : []),
...(p.meetingLink ? [`LOCATION:${p.meetingLink}`, `URL:${p.meetingLink}`] : []),
`ORGANIZER;CN=${p.organizerName}:mailto:${p.organizerEmail}`,
`ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=${p.organizerName}:mailto:${p.organizerEmail}`,
...(p.attendees || []).map((e) => `ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:${e}`),
"END:VEVENT",
"END:VCALENDAR",
];
return lines.join("\r\n");
}