import type { StructuredUiRequest, SubmitPlaybookFieldsRequest, } from "./playbook-contracts.js"; interface PlaybookCardOptions { request: StructuredUiRequest; isSubmitting: boolean; onSubmit( step: SubmitPlaybookFieldsRequest["step"], values: SubmitPlaybookFieldsRequest["values"], ): void; } /** Renders one accessible, deterministic Playbook step without network logic. */ export function createPlaybookCard(options: PlaybookCardOptions): HTMLElement { const card = createElement("section", "playbook-card"); card.setAttribute("aria-label", options.request.title); const title = createElement("strong", "playbook-title"); title.textContent = options.request.title; card.append(title); switch (options.request.kind) { case "stay_context": card.append(createStayForm(options)); break; case "contact_details": card.append(createContactForm(options)); break; case "handoff_choice": card.append(createHandoffChoices(options)); break; case "handoff_result": card.append(createHandoffResult(options.request)); } return card; } function createStayForm(options: PlaybookCardOptions): HTMLFormElement { const request = options.request; if (request.kind !== "stay_context") { throw new Error("Expected stay context request"); } const form = createElement("form", "playbook-form"); const checkIn = createInput("Check-in", "date", "checkIn"); const checkOut = createInput("Check-out", "date", "checkOut"); const adults = createInput("Adulti", "number", "adults"); const children = createInput("Bambini", "number", "children"); const rooms = createInput("Camere", "number", "rooms"); checkIn.input.required = true; checkOut.input.required = true; adults.input.required = true; adults.input.min = "1"; children.input.min = "0"; rooms.input.min = "1"; setInitialValue(checkIn.input, request.initialValues.checkIn); setInitialValue(checkOut.input, request.initialValues.checkOut); setInitialValue(adults.input, request.initialValues.adults); setInitialValue(children.input, request.initialValues.children); setInitialValue(rooms.input, request.initialValues.rooms); const submit = createSubmitButton(request.submitLabel, options.isSubmitting); form.append( checkIn.label, checkOut.label, adults.label, children.label, rooms.label, submit, ); form.addEventListener("submit", (event) => { event.preventDefault(); if (!form.reportValidity()) return; options.onSubmit("stay_context", { checkIn: checkIn.input.value, checkOut: checkOut.input.value, adults: Number(adults.input.value), ...(children.input.value === "" ? {} : { children: Number(children.input.value) }), ...(rooms.input.value === "" ? {} : { rooms: Number(rooms.input.value) }), }); }); return form; } function createContactForm(options: PlaybookCardOptions): HTMLFormElement { const request = options.request; if (request.kind !== "contact_details") { throw new Error("Expected contact details request"); } const form = createElement("form", "playbook-form"); if (request.privacyNoticeUrl === undefined) { return createLegacyContactForm(form, options); } const firstName = createInput(request.firstNameLabel!, "text", "firstName"); const lastName = createInput(request.lastNameLabel!, "text", "lastName"); const email = createInput(request.emailLabel!, "email", "email"); const phone = createInput(request.phoneLabel!, "tel", "phone"); firstName.input.required = true; lastName.input.required = true; email.input.required = true; const consent = document.createElement("label"); consent.className = "playbook-consent"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.required = true; const privacyLink = document.createElement("a"); privacyLink.href = request.privacyNoticeUrl; privacyLink.target = "_blank"; privacyLink.rel = "noopener noreferrer"; privacyLink.textContent = request.privacyNoticeLabel!; consent.append( checkbox, document.createTextNode(`${request.privacyAcknowledgementLabel!} · `), privacyLink, ); const submit = createSubmitButton(request.submitLabel, options.isSubmitting); const decline = document.createElement("button"); decline.type = "button"; decline.className = "playbook-choice"; decline.disabled = options.isSubmitting; decline.textContent = request.declineLabel!; decline.addEventListener("click", () => options.onSubmit("contact_details", { declined: true }), ); form.append( firstName.label, lastName.label, email.label, phone.label, consent, submit, decline, ); form.addEventListener("submit", (event) => { event.preventDefault(); if (!form.reportValidity()) return; options.onSubmit("contact_details", { firstName: firstName.input.value.trim(), lastName: lastName.input.value.trim(), email: email.input.value.trim(), ...(phone.input.value === "" ? {} : { phone: phone.input.value.trim() }), privacyAcknowledged: true, }); }); return form; } function createLegacyContactForm( form: HTMLFormElement, options: PlaybookCardOptions, ): HTMLFormElement { const request = options.request; if ( request.kind !== "contact_details" || request.consentLabel === undefined ) { throw new Error("Expected legacy contact details request"); } const name = createInput("Nome", "text", "name"); const email = createInput("Email", "email", "email"); const phone = createInput("Telefono", "tel", "phone"); name.input.required = true; const consent = document.createElement("label"); consent.className = "playbook-consent"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.required = true; consent.append(checkbox, document.createTextNode(request.consentLabel)); const submit = createSubmitButton(request.submitLabel, options.isSubmitting); form.append(name.label, email.label, phone.label, consent, submit); form.addEventListener("submit", (event) => { event.preventDefault(); const hasContact = email.input.value !== "" || phone.input.value !== ""; email.input.setCustomValidity( hasContact ? "" : "Inserisci email o telefono.", ); if (!form.reportValidity()) return; options.onSubmit("contact_details", { name: name.input.value.trim(), ...(email.input.value === "" ? {} : { email: email.input.value.trim() }), ...(phone.input.value === "" ? {} : { phone: phone.input.value.trim() }), consentToContact: true, }); }); return form; } function createHandoffChoices(options: PlaybookCardOptions): HTMLElement { const request = options.request; if (request.kind !== "handoff_choice") { throw new Error("Expected handoff choice request"); } const choices = createElement("div", "playbook-choices"); for (const choice of request.options) { const button = document.createElement("button"); button.type = "button"; button.className = "playbook-choice"; button.disabled = options.isSubmitting; button.textContent = choice.label; button.addEventListener("click", () => options.onSubmit("handoff_choice", { choice: choice.value }), ); choices.append(button); } return choices; } function createHandoffResult( request: Extract, ): HTMLElement { const result = createElement("div", "playbook-result"); const message = document.createElement("p"); message.textContent = request.message; result.append(message); if (request.destinationUrl !== null) { const link = document.createElement("a"); link.className = "playbook-destination"; link.href = request.destinationUrl; link.target = "_blank"; link.rel = "noopener noreferrer"; link.textContent = request.destinationLabel ?? "Continua"; result.append(link); } return result; } function createInput( labelText: string, type: "date" | "email" | "number" | "tel" | "text", name: string, ): { input: HTMLInputElement; label: HTMLLabelElement } { const label = document.createElement("label"); label.className = "playbook-field"; const text = createElement("span", "playbook-field-label"); text.textContent = labelText; const input = document.createElement("input"); input.type = type; input.name = name; input.maxLength = 320; label.append(text, input); return { input, label }; } function createSubmitButton( label: string, isSubmitting: boolean, ): HTMLButtonElement { const button = document.createElement("button"); button.type = "submit"; button.className = "playbook-submit"; button.disabled = isSubmitting; button.textContent = isSubmitting ? "Invio…" : label; return button; } function setInitialValue( input: HTMLInputElement, value: string | number | undefined, ): void { if (value !== undefined) input.value = String(value); } function createElement( tagName: K, className: string, ): HTMLElementTagNameMap[K] { const element = document.createElement(tagName); element.className = className; return element; }