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).
<