/** Standalone HTML rendering for magic link response pages. */ import { magicLinkConfig } from "./config"; import type { ChoiceSession, ChoiceOption } from "../../modules/rchoices/schemas"; import type { CalendarEvent } from "../../modules/rcal/schemas"; function esc(str: string): string { return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } const styles = ` body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f8fafc;color:#1e293b;margin:0;padding:20px} .card{max-width:500px;margin:40px auto;background:#fff;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.1);padding:32px} h1{font-size:20px;margin:0 0 4px;color:#0f172a} .sub{font-size:14px;color:#64748b;margin-bottom:24px} .opt{display:block;width:100%;padding:14px 20px;margin:8px 0;border:2px solid #e2e8f0;border-radius:10px;background:#fff;cursor:pointer;font-size:16px;text-align:left;color:#1e293b;text-decoration:none;box-sizing:border-box;transition:border-color .15s,background .15s} .opt:hover{border-color:#6366f1;background:#eef2ff} .opt.sel{border-color:#22c55e;background:#f0fdf4} .rsvp-row{display:flex;gap:10px;margin:16px 0} .rsvp-btn{flex:1;padding:14px;border:2px solid #e2e8f0;border-radius:10px;background:#fff;cursor:pointer;font-size:15px;text-align:center;text-decoration:none;color:#1e293b;transition:border-color .15s,background .15s} .rsvp-btn:hover{border-color:#6366f1;background:#eef2ff} .rsvp-btn.sel{border-color:#22c55e;background:#f0fdf4} .banner{padding:12px;border-radius:8px;margin-bottom:20px;font-size:14px} .banner-ok{background:#22c55e;color:#fff} .banner-info{background:#3b82f6;color:#fff} .meta{font-size:13px;color:#64748b;margin:4px 0} .ft{text-align:center;font-size:12px;color:#94a3b8;margin-top:24px} .label{font-size:13px;color:#94a3b8;margin-top:16px;margin-bottom:4px} `; // ── Poll page ── export function renderPollPage( session: ChoiceSession, token: string, respondentName: string, existingChoice?: string, justVoted?: string, ): string { let banner = ""; if (justVoted) { const opt = session.options.find((o) => o.id === justVoted); banner = ``; } else if (existingChoice) { const opt = session.options.find((o) => o.id === existingChoice); banner = ``; } const optionButtons = session.options .map((opt) => { const selected = (existingChoice === opt.id || justVoted === opt.id) ? " sel" : ""; return `
`; }) .join("\n"); return ` ${esc(session.title)} - rChoices
${banner}

${esc(session.title)}

Hi ${esc(respondentName)} — tap your choice below
${session.options.length} option${session.options.length === 1 ? "" : "s"} · ${session.type} vote
${optionButtons}
rChoices · rspace.online · Link expires in ${magicLinkConfig.tokenExpiryDays} days
`; } // ── RSVP page ── const rsvpLabels: Record = { yes: "Going", no: "Not Going", maybe: "Maybe" }; const rsvpEmoji: Record = { yes: "✓", no: "✗", maybe: "?" }; export function renderRsvpPage( event: CalendarEvent, token: string, respondentName: string, currentStatus?: string, justResponded?: string, ): string { let banner = ""; if (justResponded) { banner = ``; } else if (currentStatus && currentStatus !== "pending") { banner = ``; } const startDate = event.startTime ? new Date(event.startTime).toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" }) : ""; const startTime = event.startTime && !event.allDay ? new Date(event.startTime).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }) : ""; const endTime = event.endTime && !event.allDay ? new Date(event.endTime).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }) : ""; const timeLine = event.allDay ? `

All day · ${startDate}

` : `

${startDate}${startTime ? ` · ${startTime}` : ""}${endTime ? ` – ${endTime}` : ""}

`; const location = event.locationName ? `

${esc(event.locationName)}

` : event.isVirtual && event.virtualUrl ? `

Virtual · ${esc(event.virtualPlatform || "Join")}

` : ""; const buttons = ["yes", "no", "maybe"] .map((status) => { const selected = (currentStatus === status || justResponded === status) ? " sel" : ""; return `
`; }) .join("\n"); return ` RSVP: ${esc(event.title)} - rCal
${banner}

${esc(event.title)}

${timeLine} ${location} ${event.description ? `

${esc(event.description)}

` : ""}
Hi ${esc(respondentName)} — will you attend?
${buttons}
rCal · rspace.online · Link expires in ${magicLinkConfig.tokenExpiryDays} days
`; } // ── Error page ── export function renderError(title: string, message: string): string { return ` Error - rSpace

${esc(title)}

${esc(message)}

rSpace · rspace.online
`; }