96 lines
3.0 KiB
TypeScript
96 lines
3.0 KiB
TypeScript
/**
|
|
* <folk-schedule-booking> — public booking page.
|
|
*
|
|
* Phase A: stub that fetches public settings + availability and shows a
|
|
* placeholder date picker. Phase B ports the full UI from
|
|
* schedule-jeffemmett/src/app/page.tsx (month calendar, slot list, timezone
|
|
* toggle, world map, booking form).
|
|
*/
|
|
|
|
interface PublicSettings {
|
|
displayName: string;
|
|
bookingMessage: string;
|
|
slotDurationMin: number;
|
|
maxAdvanceDays: number;
|
|
minNoticeHours: number;
|
|
timezone: string;
|
|
}
|
|
|
|
class FolkScheduleBooking extends HTMLElement {
|
|
private shadow: ShadowRoot;
|
|
private space = "";
|
|
private settings: PublicSettings | null = null;
|
|
|
|
constructor() {
|
|
super();
|
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.space = this.getAttribute("space") || "demo";
|
|
void this.load();
|
|
}
|
|
|
|
private async load() {
|
|
try {
|
|
const base = this.apiBase();
|
|
const res = await fetch(`${base}/settings/public`);
|
|
this.settings = res.ok ? await res.json() : null;
|
|
} catch {
|
|
this.settings = null;
|
|
}
|
|
this.render();
|
|
}
|
|
|
|
private apiBase(): string {
|
|
const path = window.location.pathname;
|
|
const match = path.match(/^(\/[^/]+)?\/rschedule/);
|
|
return `${match?.[0] || "/rschedule"}/api`;
|
|
}
|
|
|
|
private render() {
|
|
const s = this.settings;
|
|
const name = s?.displayName || this.space;
|
|
const msg = s?.bookingMessage || "Book a time to chat.";
|
|
const duration = s?.slotDurationMin ?? 30;
|
|
this.shadow.innerHTML = `
|
|
<style>
|
|
:host { display: block; color: #e2e8f0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; min-height: 100vh; background: #0b1120; }
|
|
.wrap { max-width: 980px; margin: 0 auto; padding: 48px 24px; }
|
|
.card { background: #111827; border: 1px solid rgba(148,163,184,0.12); border-radius: 16px; padding: 32px; }
|
|
h1 { margin: 0 0 8px; font-size: 1.6rem; background: linear-gradient(to right, #06b6d4, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
|
|
.meta { display: flex; gap: 16px; color: #94a3b8; font-size: 0.9rem; margin-bottom: 16px; }
|
|
.msg { color: #cbd5e1; margin: 0 0 24px; white-space: pre-wrap; }
|
|
.placeholder { padding: 40px; text-align: center; border: 1px dashed rgba(148,163,184,0.25); border-radius: 10px; color: #94a3b8; }
|
|
</style>
|
|
<div class="wrap">
|
|
<div class="card">
|
|
<h1>Book with ${escapeHtml(name)}</h1>
|
|
<div class="meta">
|
|
<span>⏱ ${duration} min</span>
|
|
${s?.timezone ? `<span>🌐 ${escapeHtml(s.timezone)}</span>` : ""}
|
|
</div>
|
|
<p class="msg">${escapeHtml(msg)}</p>
|
|
<div class="placeholder">
|
|
Date picker and slot list coming in Phase B.<br>
|
|
Admin: <a href="admin" style="color:#06b6d4">configure availability</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
function escapeHtml(s: string): string {
|
|
return String(s)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
customElements.define("folk-schedule-booking", FolkScheduleBooking);
|
|
|
|
export {};
|