Dental Scheduling Widget
/* ----------------- CONFIG ----------------- */ const SCHEDULING_PORTAL_BASE = "https://example.com/schedule?phone="; // fallback link // Static dummy availability const dummySlots = { existing: { "2025-07-01": ["09:00-09:15", "11:00-11:15", "15:00-15:15"], "2025-07-02": ["09:30-09:45", "13:00-13:15", "16:00-16:15"], "2025-07-03": ["10:00-10:15", "12:30-12:45", "14:00-14:15", "16:30-16:45"] }, new: { "2025-07-01": ["10:00-10:30", "14:00-14:30"], "2025-07-02": ["11:00-11:30", "15:30-16:00"], "2025-07-03": ["09:00-09:30", "13:30-14:00"] }, emergency: { "2025-07-01": ["10:30-11:00", "12:00-12:30"], "2025-07-02": ["09:00-09:30", "14:30-15:00"], "2025-07-03": ["11:30-12:00", "15:00-15:30"] } }; /* ----------------- STATE ----------------- */ const urlParams = new URLSearchParams(window.location.search); const headerPhone = urlParams.get('phone_number') || urlParams.get('phone') || ""; let state = { step: 1, firstName: "", lastName: "", dob: "", phone: headerPhone, phoneFromHeader: Boolean(headerPhone), isNew: false, isEmergency: false, selectedDate: "", selectedTime: "" }; const container = document.getElementById("widget-container"); /* ----------------- HELPERS ----------------- */ const createEl = (tag, props = {}, children = []) => { const el = document.createElement(tag); Object.assign(el, props); (children || []).forEach(c => el.appendChild(typeof c === "string" ? document.createTextNode(c) : c)); return el; }; const addDebug = (parent) => { parent.appendChild(createEl("pre", {className:"debug", innerText: JSON.stringify(state,null,2)})); }; const render = () => { container.innerHTML = ""; if (state.step === 1) renderForm(); else if (state.step === 2) renderCalendar(); else if (state.step === 3) renderReview(); else if (state.step === 4) renderConfirmation(); }; // Mock APIs const apiCreatePatient = (payload) => new Promise(res => setTimeout(() => res({patientId: Date.now()}), 800)); const apiBookAppointment = (payload) => new Promise(res => setTimeout(() => res({appointmentId: Date.now()}), 800)); /* ----------------- STEP 1: FORM ----------------- */ const renderForm = () => { const form = createEl("form"); form.appendChild(createEl("h2", {innerText: "Book a Dental Appointment"})); form.appendChild(createEl("label", {}, ["First Name", createEl("input", {type:"text",required:true,oninput:e=>state.firstName=e.target.value})])); form.appendChild(createEl("label", {}, ["Last Name", createEl("input", {type:"text",required:true,oninput:e=>state.lastName=e.target.value})])); form.appendChild(createEl("label", {}, ["Date of Birth", createEl("input", {type:"date",required:true,oninput:e=>state.dob=e.target.value})])); // Phone field only if not provided via header if(!state.phoneFromHeader){ form.appendChild(createEl("label", {}, ["Phone Number", createEl("input", {type:"tel",required:true,placeholder:"e.g. 555-123-4567",oninput:e=>state.phone=e.target.value})])); } const cbNew = createEl("label", {style:"flex-direction:row;align-items:center"}, [createEl("input", {type:"checkbox",onchange:e=>state.isNew=e.target.checked}), " New Patient"]); const cbEmerg = createEl("label", {style:"flex-direction:row;align-items:center"}, [createEl("input", {type:"checkbox",onchange:e=>state.isEmergency=e.target.checked}), " Emergency"]); form.appendChild(cbNew); form.appendChild(cbEmerg); const btn = createEl("button", {className:"btn", innerText:"Continue"}); btn.addEventListener("click", async (e) => { e.preventDefault(); if (!form.checkValidity()) { form.reportValidity(); return; } btn.disabled=true;btn.innerText="Saving..."; try { if (state.isNew) { await apiCreatePatient({firstName: state.firstName, lastName: state.lastName, dob: state.dob, phone: state.phone}); } state.step = 2; render(); } finally {btn.disabled=false;btn.innerText="Continue";} }); form.appendChild(btn); form.appendChild(createEl("div", {className:"link"}, [createEl("a", {href:SCHEDULING_PORTAL_BASE + encodeURIComponent(state.phone||""), target:"_blank", innerText:"Prefer a different experience? Click here."})])); addDebug(form); container.appendChild(form); }; /* ----------------- STEP 2: CALENDAR ----------------- */ const renderCalendar = () => { const step = createEl("div", {className:"step"}); step.appendChild(createEl("h2", {innerText:"Select a Time"})); const availabilityType = state.isEmergency ? "emergency" : (state.isNew ? "new" : "existing"); const dates = Object.keys(dummySlots[availabilityType]); const calWrap = createEl("div", {className:"calendar-scroll"}); dates.forEach(dateStr => { calWrap.appendChild(createEl("div", {className:"date-item"}, [new Date(dateStr).toDateString()])); const grid = createEl("div", {className:"time-grid"}); dummySlots[availabilityType][dateStr].forEach(slot => { const btn = createEl("button", {className:"time-btn", innerText:slot}); btn.addEventListener("click", () => { state.selectedDate = dateStr; state.selectedTime = slot; state.step = 3; render(); }); grid.appendChild(btn); }); calWrap.appendChild(grid); }); step.appendChild(calWrap); addDebug(step); container.appendChild(step); }; /* ----------------- STEP 3: REVIEW ----------------- */ const renderReview = () => { const step = createEl("div", {className:"step"}); step.appendChild(createEl("h2", {innerText:"Review & Book"})); const summary = `\n${state.firstName} ${state.lastName}\nDOB: ${state.dob}\nPhone: ${state.phone}\nNew Patient: ${state.isNew ? "Yes" : "No"}\nEmergency: ${state.isEmergency ? "Yes" : "No"}\nAppointment: ${new Date(state.selectedDate). <