/** * — admin dashboard for rSchedule. * * Passkey-gated (server enforces moderator role on every admin API call). * Phase A: minimal shell with tab placeholders. Phase E builds full UI * (availability rules, overrides, bookings list, settings, gcal connect). */ class FolkScheduleAdmin extends HTMLElement { private shadow: ShadowRoot; private space = ""; private tab: "overview" | "availability" | "bookings" | "calendar" | "settings" = "overview"; private err = ""; constructor() { super(); this.shadow = this.attachShadow({ mode: "open" }); } connectedCallback() { this.space = this.getAttribute("space") || "demo"; this.render(); } private authHeaders(): Record { try { const sess = JSON.parse(localStorage.getItem("encryptid_session") || "{}"); const token = sess?.token; return token ? { Authorization: `Bearer ${token}` } : {}; } catch { return {}; } } private async ping() { const path = window.location.pathname; const m = path.match(/^(\/[^/]+)?\/rschedule/); const base = `${m?.[0] || "/rschedule"}/api/admin`; const res = await fetch(`${base}/settings`, { headers: this.authHeaders() }); if (res.status === 401) { this.err = "Sign in to manage this booking page."; return; } if (res.status === 403) { this.err = "You need moderator or higher role in this space."; return; } if (!res.ok) { this.err = `Error ${res.status}`; return; } this.err = ""; } private render() { void this.ping().then(() => this.paint()); this.paint(); } private paint() { this.shadow.innerHTML = `

rSchedule admin · ${escapeHtml(this.space)}

Configure availability, bookings, and Google Calendar for this space.

${this.err ? `
${escapeHtml(this.err)}
` : ""}
${this.tabBtn("overview", "Overview")} ${this.tabBtn("availability", "Availability")} ${this.tabBtn("bookings", "Bookings")} ${this.tabBtn("calendar", "Google Calendar")} ${this.tabBtn("settings", "Settings")}
${escapeHtml(this.tab)}
Full admin UI arrives in Phase E. For now, this confirms auth gate works.
`; this.shadow.querySelectorAll(".tab").forEach((btn) => { btn.addEventListener("click", () => { this.tab = btn.dataset.tab as any; this.paint(); }); }); } private tabBtn(id: string, label: string): string { return ``; } } function escapeHtml(s: string): string { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } customElements.define("folk-schedule-admin", FolkScheduleAdmin); export {};