@lifestreamdynamics/booking-widget 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/lsv-booking.js +101 -86
- package/dist/lsv-booking.umd.cjs +2 -2
- package/package.json +4 -1
package/dist/index.d.ts
CHANGED
package/dist/lsv-booking.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var v = Object.defineProperty;
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
const
|
|
2
|
+
var k = (h, u, e) => u in h ? v(h, u, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[u] = e;
|
|
3
|
+
var m = (h, u, e) => k(h, typeof u != "symbol" ? u + "" : u, e);
|
|
4
|
+
const C = `
|
|
5
5
|
:host {
|
|
6
6
|
display: block;
|
|
7
7
|
font-family: system-ui, -apple-system, sans-serif;
|
|
@@ -393,22 +393,22 @@ const k = `
|
|
|
393
393
|
gap: 0.5rem;
|
|
394
394
|
margin-top: 1rem;
|
|
395
395
|
}
|
|
396
|
-
`, y = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"],
|
|
396
|
+
`, y = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"], E = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
397
397
|
class f extends HTMLElement {
|
|
398
398
|
constructor() {
|
|
399
399
|
super();
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
400
|
+
m(this, "root");
|
|
401
|
+
m(this, "abortController", null);
|
|
402
|
+
m(this, "step", "slots");
|
|
403
|
+
m(this, "slots", []);
|
|
404
|
+
m(this, "selectedSlot", null);
|
|
405
|
+
m(this, "selectedDate", "");
|
|
406
|
+
m(this, "selectedTime", "");
|
|
407
|
+
m(this, "availableTimes", []);
|
|
408
|
+
m(this, "isLoading", !1);
|
|
409
|
+
m(this, "errorMsg", "");
|
|
410
410
|
// ── Event handling ────────────────────────────────────────────────────────
|
|
411
|
-
|
|
411
|
+
m(this, "handleRootClick", (e) => {
|
|
412
412
|
const r = e.target.closest("[data-action]");
|
|
413
413
|
r && this.handleAction(r.dataset.action ?? "", r);
|
|
414
414
|
});
|
|
@@ -447,80 +447,95 @@ class f extends HTMLElement {
|
|
|
447
447
|
setError(e) {
|
|
448
448
|
this.errorMsg = e, this.isLoading = !1, this.render();
|
|
449
449
|
}
|
|
450
|
+
// ── Signal helpers ────────────────────────────────────────────────────────
|
|
451
|
+
getSignal(e = 15e3) {
|
|
452
|
+
var r;
|
|
453
|
+
const t = AbortSignal.timeout(e);
|
|
454
|
+
return (r = this.abortController) != null && r.signal ? AbortSignal.any([this.abortController.signal, t]) : t;
|
|
455
|
+
}
|
|
450
456
|
// ── API calls ─────────────────────────────────────────────────────────────
|
|
451
457
|
async loadSlots() {
|
|
452
|
-
var e;
|
|
453
458
|
this.setLoading(!0);
|
|
454
459
|
try {
|
|
455
|
-
const
|
|
456
|
-
if (!
|
|
457
|
-
const
|
|
458
|
-
this.slots =
|
|
459
|
-
} catch {
|
|
460
|
-
|
|
460
|
+
const e = await fetch(`${this.baseApiPath}/booking-slots`, { signal: this.getSignal() });
|
|
461
|
+
if (!e.ok) throw new Error(`HTTP ${e.status}`);
|
|
462
|
+
const t = await e.json();
|
|
463
|
+
this.slots = t.slots, this.errorMsg = "";
|
|
464
|
+
} catch (e) {
|
|
465
|
+
const t = e instanceof Error ? e.message : "Failed to load booking slots. Please try again.";
|
|
466
|
+
this.dispatchEvent(new CustomEvent("lsv-booking-error", {
|
|
467
|
+
bubbles: !0,
|
|
468
|
+
composed: !0,
|
|
469
|
+
detail: { message: t, step: "slots" }
|
|
470
|
+
})), this.setError("Failed to load booking slots. Please try again.");
|
|
461
471
|
return;
|
|
462
472
|
}
|
|
463
473
|
this.isLoading = !1, this.render();
|
|
464
474
|
}
|
|
465
475
|
async loadTimes(e, t) {
|
|
466
|
-
var r;
|
|
467
476
|
this.setLoading(!0);
|
|
468
477
|
try {
|
|
469
|
-
const
|
|
478
|
+
const r = await fetch(
|
|
470
479
|
`${this.baseApiPath}/booking-slots/${e}/availability?date=${t}`,
|
|
471
|
-
{ signal:
|
|
480
|
+
{ signal: this.getSignal() }
|
|
472
481
|
);
|
|
473
|
-
if (!
|
|
474
|
-
const
|
|
475
|
-
this.availableTimes = o.times, this.errorMsg = "";
|
|
476
|
-
} catch {
|
|
477
|
-
|
|
482
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
483
|
+
const s = await r.json();
|
|
484
|
+
s.times.length > 0 && typeof s.times[0] == "object" ? this.availableTimes = s.times.map((o) => o.startAt) : this.availableTimes = s.times, this.errorMsg = "";
|
|
485
|
+
} catch (r) {
|
|
486
|
+
const s = r instanceof Error ? r.message : "Failed to load available times. Please try again.";
|
|
487
|
+
this.dispatchEvent(new CustomEvent("lsv-booking-error", {
|
|
488
|
+
bubbles: !0,
|
|
489
|
+
composed: !0,
|
|
490
|
+
detail: { message: s, step: "time" }
|
|
491
|
+
})), this.setError("Failed to load available times. Please try again.");
|
|
478
492
|
return;
|
|
479
493
|
}
|
|
480
494
|
this.isLoading = !1, this.step = "time", this.render();
|
|
481
495
|
}
|
|
482
496
|
async submitBooking(e, t, r, s) {
|
|
483
|
-
var c;
|
|
484
497
|
if (!this.selectedSlot || !this.selectedDate || !this.selectedTime) return;
|
|
485
498
|
this.setLoading(!0);
|
|
486
|
-
const o = (/* @__PURE__ */ new Date(`${this.selectedDate}T${this.selectedTime}`)).toISOString(), a = this.selectedSlot.id,
|
|
499
|
+
const o = this.selectedTime.includes("T") ? this.selectedTime : (/* @__PURE__ */ new Date(`${this.selectedDate}T${this.selectedTime}`)).toISOString(), a = this.selectedSlot.id, i = {
|
|
487
500
|
guestName: e,
|
|
488
501
|
guestEmail: t,
|
|
489
502
|
startAt: o
|
|
490
503
|
};
|
|
491
|
-
r && (
|
|
504
|
+
r && (i.guestPhone = r), s && (i.notes = s);
|
|
492
505
|
try {
|
|
493
|
-
const
|
|
506
|
+
const c = await fetch(
|
|
494
507
|
`${this.baseApiPath}/booking-slots/${a}/book`,
|
|
495
508
|
{
|
|
496
509
|
method: "POST",
|
|
497
510
|
headers: { "Content-Type": "application/json" },
|
|
498
|
-
body: JSON.stringify(
|
|
499
|
-
signal:
|
|
511
|
+
body: JSON.stringify(i),
|
|
512
|
+
signal: this.getSignal()
|
|
500
513
|
}
|
|
501
514
|
);
|
|
502
|
-
if (!
|
|
503
|
-
const
|
|
504
|
-
throw new Error(
|
|
515
|
+
if (!c.ok) {
|
|
516
|
+
const d = await c.json().catch(() => ({}));
|
|
517
|
+
throw new Error(d.error ?? d.message ?? `HTTP ${c.status}`);
|
|
505
518
|
}
|
|
506
|
-
const
|
|
507
|
-
this.isLoading = !1, this.step = "success", this.render()
|
|
519
|
+
const n = await c.json();
|
|
520
|
+
this.isLoading = !1, this.step = "success", this.render();
|
|
521
|
+
const l = new Date(n.startAt), p = new Date(l.getTime() + (this.selectedSlot.durationMin ?? 0) * 60 * 1e3);
|
|
522
|
+
this.dispatchEvent(new CustomEvent("lsv-booking-submitted", {
|
|
508
523
|
bubbles: !0,
|
|
509
524
|
composed: !0,
|
|
510
525
|
detail: {
|
|
511
526
|
bookingId: "",
|
|
512
|
-
startAt:
|
|
513
|
-
endAt:
|
|
527
|
+
startAt: n.startAt,
|
|
528
|
+
endAt: p.toISOString(),
|
|
514
529
|
slotTitle: this.selectedSlot.title
|
|
515
530
|
}
|
|
516
531
|
}));
|
|
517
|
-
} catch (
|
|
518
|
-
const
|
|
532
|
+
} catch (c) {
|
|
533
|
+
const n = c instanceof Error ? c.message : "Booking failed. Please try again.";
|
|
519
534
|
this.dispatchEvent(new CustomEvent("lsv-booking-error", {
|
|
520
535
|
bubbles: !0,
|
|
521
536
|
composed: !0,
|
|
522
|
-
detail: { message:
|
|
523
|
-
})), this.setError(
|
|
537
|
+
detail: { message: n, step: "form" }
|
|
538
|
+
})), this.setError(n);
|
|
524
539
|
}
|
|
525
540
|
}
|
|
526
541
|
// ── Date helpers ──────────────────────────────────────────────────────────
|
|
@@ -562,7 +577,7 @@ class f extends HTMLElement {
|
|
|
562
577
|
// ── Render ────────────────────────────────────────────────────────────────
|
|
563
578
|
render() {
|
|
564
579
|
const e = document.createElement("style");
|
|
565
|
-
e.textContent =
|
|
580
|
+
e.textContent = C;
|
|
566
581
|
const t = document.createElement("div");
|
|
567
582
|
if (t.className = "widget", this.step === "success")
|
|
568
583
|
t.appendChild(this.renderSuccess());
|
|
@@ -589,14 +604,14 @@ class f extends HTMLElement {
|
|
|
589
604
|
}
|
|
590
605
|
renderStepIndicator() {
|
|
591
606
|
const e = ["slots", "date", "time", "form"], t = e.indexOf(this.step), r = document.createElement("div");
|
|
592
|
-
return r.className = "step-indicator", e.forEach((s, o) => {
|
|
593
|
-
const a = document.createElement("div");
|
|
594
|
-
|
|
607
|
+
return r.className = "step-indicator", r.setAttribute("aria-label", "Booking progress"), r.setAttribute("role", "navigation"), e.forEach((s, o) => {
|
|
608
|
+
const a = o === t, i = document.createElement("div");
|
|
609
|
+
i.className = `step-dot${a ? " active" : o < t ? " done" : ""}`, i.setAttribute("aria-label", `Step ${o + 1}${a ? " (current)" : ""}`), a && i.setAttribute("aria-current", "step"), r.appendChild(i);
|
|
595
610
|
}), r;
|
|
596
611
|
}
|
|
597
612
|
renderLoading() {
|
|
598
613
|
const e = document.createElement("div");
|
|
599
|
-
e.className = "loading";
|
|
614
|
+
e.className = "loading", e.setAttribute("aria-live", "polite"), e.setAttribute("role", "status");
|
|
600
615
|
const t = document.createElement("div");
|
|
601
616
|
t.className = "spinner";
|
|
602
617
|
const r = document.createElement("span");
|
|
@@ -604,7 +619,7 @@ class f extends HTMLElement {
|
|
|
604
619
|
}
|
|
605
620
|
renderError() {
|
|
606
621
|
const e = document.createDocumentFragment(), t = document.createElement("div");
|
|
607
|
-
t.className = "error-box", t.textContent = this.errorMsg, e.appendChild(t);
|
|
622
|
+
t.className = "error-box", t.setAttribute("aria-live", "assertive"), t.setAttribute("role", "alert"), t.textContent = this.errorMsg, e.appendChild(t);
|
|
608
623
|
const r = document.createElement("button");
|
|
609
624
|
r.className = "btn-retry", r.dataset.action = "retry", r.textContent = "Try again", e.appendChild(r);
|
|
610
625
|
const s = document.createElement("div");
|
|
@@ -620,12 +635,12 @@ class f extends HTMLElement {
|
|
|
620
635
|
s.className = "slot-list", this.slots.forEach((o) => {
|
|
621
636
|
const a = document.createElement("button");
|
|
622
637
|
a.className = "slot-card", a.dataset.action = "select-slot", a.dataset.slotId = o.id;
|
|
623
|
-
const
|
|
624
|
-
|
|
638
|
+
const i = document.createElement("p");
|
|
639
|
+
i.className = "slot-card-title", i.textContent = o.title;
|
|
625
640
|
const c = document.createElement("p");
|
|
626
641
|
c.className = "slot-card-meta";
|
|
627
|
-
const
|
|
628
|
-
if (c.textContent = `${o.durationMin} min · ${
|
|
642
|
+
const n = this.formatDayList(o.daysOfWeek);
|
|
643
|
+
if (c.textContent = `${o.durationMin} min · ${n} · ${o.startTime}–${o.endTime}`, a.appendChild(i), o.description) {
|
|
629
644
|
const l = document.createElement("p");
|
|
630
645
|
l.className = "slot-card-meta", l.style.marginTop = "0.25rem", l.textContent = o.description, a.appendChild(l);
|
|
631
646
|
}
|
|
@@ -653,29 +668,29 @@ class f extends HTMLElement {
|
|
|
653
668
|
renderDate() {
|
|
654
669
|
var c;
|
|
655
670
|
const e = document.createDocumentFragment(), t = document.createElement("button");
|
|
656
|
-
t.className = "btn-back", t.dataset.action = "back-to-slots", t.textContent = "← Back", e.appendChild(t);
|
|
671
|
+
t.className = "btn-back", t.dataset.action = "back-to-slots", t.setAttribute("aria-label", "Go back"), t.textContent = "← Back", e.appendChild(t);
|
|
657
672
|
const r = document.createElement("p");
|
|
658
673
|
r.className = "section-title", r.textContent = "Select a date", e.appendChild(r);
|
|
659
674
|
const s = this.getNextDates(), o = document.createElement("div");
|
|
660
|
-
o.className = "date-grid",
|
|
675
|
+
o.className = "date-grid", E.forEach((n) => {
|
|
661
676
|
const l = document.createElement("div");
|
|
662
|
-
l.className = "date-header", l.textContent =
|
|
677
|
+
l.className = "date-header", l.textContent = n, o.appendChild(l);
|
|
663
678
|
});
|
|
664
679
|
const a = ((c = s[0]) == null ? void 0 : c.dayOfWeek) ?? 0;
|
|
665
|
-
for (let
|
|
680
|
+
for (let n = 0; n < a; n++) {
|
|
666
681
|
const l = document.createElement("div");
|
|
667
682
|
l.className = "date-cell empty", o.appendChild(l);
|
|
668
683
|
}
|
|
669
|
-
s.forEach(({ date:
|
|
670
|
-
const
|
|
671
|
-
|
|
684
|
+
s.forEach(({ date: n, label: l, dayOfWeek: p }) => {
|
|
685
|
+
const d = document.createElement("button"), b = this.isDateAllowed(p);
|
|
686
|
+
d.className = `date-cell${b ? "" : " disabled"}${n === this.selectedDate ? " selected" : ""}`, d.textContent = (/* @__PURE__ */ new Date(`${n}T12:00:00`)).getDate().toString(), d.title = l, d.setAttribute("aria-label", `Select ${l}`), b ? (d.dataset.action = "select-date", d.dataset.date = n) : d.disabled = !0, o.appendChild(d);
|
|
672
687
|
}), e.appendChild(o);
|
|
673
|
-
const
|
|
674
|
-
return
|
|
688
|
+
const i = document.createElement("div");
|
|
689
|
+
return i.appendChild(e), i;
|
|
675
690
|
}
|
|
676
691
|
renderTime() {
|
|
677
692
|
const e = document.createDocumentFragment(), t = document.createElement("button");
|
|
678
|
-
t.className = "btn-back", t.dataset.action = "back-to-date", t.textContent = "← Back", e.appendChild(t);
|
|
693
|
+
t.className = "btn-back", t.dataset.action = "back-to-date", t.setAttribute("aria-label", "Go back"), t.textContent = "← Back", e.appendChild(t);
|
|
679
694
|
const r = document.createElement("p");
|
|
680
695
|
if (r.className = "section-title", r.textContent = `Available times · ${this.formatDate(this.selectedDate)}`, e.appendChild(r), this.availableTimes.length === 0) {
|
|
681
696
|
const o = document.createElement("p");
|
|
@@ -683,8 +698,8 @@ class f extends HTMLElement {
|
|
|
683
698
|
} else {
|
|
684
699
|
const o = document.createElement("div");
|
|
685
700
|
o.className = "time-grid", this.availableTimes.forEach((a) => {
|
|
686
|
-
const
|
|
687
|
-
|
|
701
|
+
const i = document.createElement("button");
|
|
702
|
+
i.className = `time-btn${a === this.selectedTime ? " selected" : ""}`, i.dataset.action = "select-time", i.dataset.time = a, i.textContent = this.formatTime(a), a === this.selectedTime && i.setAttribute("aria-pressed", "true"), o.appendChild(i);
|
|
688
703
|
}), e.appendChild(o);
|
|
689
704
|
}
|
|
690
705
|
if (this.selectedTime) {
|
|
@@ -697,37 +712,37 @@ class f extends HTMLElement {
|
|
|
697
712
|
return s.appendChild(e), s;
|
|
698
713
|
}
|
|
699
714
|
renderForm() {
|
|
700
|
-
var
|
|
715
|
+
var b, g;
|
|
701
716
|
const e = document.createDocumentFragment(), t = document.createElement("button");
|
|
702
|
-
t.className = "btn-back", t.dataset.action = "back-to-time", t.textContent = "← Back", e.appendChild(t);
|
|
717
|
+
t.className = "btn-back", t.dataset.action = "back-to-time", t.setAttribute("aria-label", "Go back"), t.textContent = "← Back", e.appendChild(t);
|
|
703
718
|
const r = document.createElement("div");
|
|
704
719
|
r.className = "booking-summary";
|
|
705
720
|
const s = document.createElement("strong");
|
|
706
|
-
s.textContent = ((
|
|
721
|
+
s.textContent = ((b = this.selectedSlot) == null ? void 0 : b.title) ?? "", r.appendChild(s);
|
|
707
722
|
const o = document.createElement("span");
|
|
708
723
|
o.textContent = `${this.formatDate(this.selectedDate)} at ${this.formatTime(this.selectedTime)}`, r.appendChild(o), e.appendChild(r);
|
|
709
724
|
const a = document.createElement("form");
|
|
710
|
-
a.id = "lsv-booking-form", a.appendChild(this.makeField("guestName", "Full name", "text", !0)), a.appendChild(this.makeField("guestEmail", "Email address", "email", !0)), (
|
|
711
|
-
const
|
|
712
|
-
|
|
725
|
+
a.id = "lsv-booking-form", a.appendChild(this.makeField("guestName", "Full name", "text", !0)), a.appendChild(this.makeField("guestEmail", "Email address", "email", !0)), (g = this.selectedSlot) != null && g.requirePhone && a.appendChild(this.makeField("guestPhone", "Phone number", "tel", !0));
|
|
726
|
+
const i = document.createElement("div");
|
|
727
|
+
i.className = "form-group";
|
|
713
728
|
const c = document.createElement("label");
|
|
714
729
|
c.className = "form-label", c.setAttribute("for", "lsv-notes"), c.textContent = "Notes (optional)";
|
|
715
|
-
const
|
|
716
|
-
|
|
730
|
+
const n = document.createElement("textarea");
|
|
731
|
+
n.className = "form-textarea", n.id = "lsv-notes", n.name = "notes", n.rows = 3, n.placeholder = "Anything you'd like to share…", i.appendChild(c), i.appendChild(n), a.appendChild(i);
|
|
717
732
|
const l = document.createElement("div");
|
|
718
733
|
l.className = "actions";
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
const
|
|
722
|
-
return
|
|
734
|
+
const p = document.createElement("button");
|
|
735
|
+
p.type = "submit", p.className = "btn-primary", p.textContent = "Confirm Booking", l.appendChild(p), a.appendChild(l), e.appendChild(a);
|
|
736
|
+
const d = document.createElement("div");
|
|
737
|
+
return d.appendChild(e), d;
|
|
723
738
|
}
|
|
724
739
|
makeField(e, t, r, s) {
|
|
725
740
|
const o = document.createElement("div");
|
|
726
741
|
o.className = "form-group";
|
|
727
742
|
const a = document.createElement("label");
|
|
728
743
|
a.className = "form-label", a.setAttribute("for", `lsv-${e}`), a.textContent = s ? `${t} *` : t;
|
|
729
|
-
const
|
|
730
|
-
return
|
|
744
|
+
const i = document.createElement("input");
|
|
745
|
+
return i.className = "form-input", i.type = r, i.id = `lsv-${e}`, i.name = e, i.required = s, i.autocomplete = e === "guestName" ? "name" : e === "guestEmail" ? "email" : e === "guestPhone" ? "tel" : "off", o.appendChild(a), o.appendChild(i), o;
|
|
731
746
|
}
|
|
732
747
|
renderSuccess() {
|
|
733
748
|
const e = document.createElement("div");
|
|
@@ -743,8 +758,8 @@ class f extends HTMLElement {
|
|
|
743
758
|
const e = this.root.querySelector("#lsv-booking-form");
|
|
744
759
|
e && e.addEventListener("submit", (t) => {
|
|
745
760
|
t.preventDefault();
|
|
746
|
-
const r = new FormData(e), s = (r.get("guestName") ?? "").trim(), o = (r.get("guestEmail") ?? "").trim(), a = (r.get("guestPhone") ?? "").trim(),
|
|
747
|
-
!s || !o || this.submitBooking(s, o, a,
|
|
761
|
+
const r = new FormData(e), s = (r.get("guestName") ?? "").trim(), o = (r.get("guestEmail") ?? "").trim(), a = (r.get("guestPhone") ?? "").trim(), i = (r.get("notes") ?? "").trim();
|
|
762
|
+
!s || !o || this.submitBooking(s, o, a, i);
|
|
748
763
|
});
|
|
749
764
|
}
|
|
750
765
|
handleAction(e, t) {
|
|
@@ -785,7 +800,7 @@ class f extends HTMLElement {
|
|
|
785
800
|
}
|
|
786
801
|
}
|
|
787
802
|
}
|
|
788
|
-
|
|
803
|
+
m(f, "observedAttributes", ["api-url", "profile-slug", "vault-slug", "theme"]);
|
|
789
804
|
customElements.get("lsv-booking") || customElements.define("lsv-booking", f);
|
|
790
805
|
export {
|
|
791
806
|
f as LsvBooking
|
package/dist/lsv-booking.umd.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(p,m){typeof exports=="object"&&typeof module<"u"?m(exports):typeof define=="function"&&define.amd?define(["exports"],m):(p=typeof globalThis<"u"?globalThis:p||self,m(p.LsvBooking={}))})(this,(function(p){"use strict";var y=Object.defineProperty;var C=(p,m,b)=>m in p?y(p,m,{enumerable:!0,configurable:!0,writable:!0,value:b}):p[m]=b;var u=(p,m,b)=>C(p,typeof m!="symbol"?m+"":m,b);const m=`
|
|
2
2
|
:host {
|
|
3
3
|
display: block;
|
|
4
4
|
font-family: system-ui, -apple-system, sans-serif;
|
|
@@ -390,4 +390,4 @@
|
|
|
390
390
|
gap: 0.5rem;
|
|
391
391
|
margin-top: 1rem;
|
|
392
392
|
}
|
|
393
|
-
`,g=["sun","mon","tue","wed","thu","fri","sat"],C=["Su","Mo","Tu","We","Th","Fr","Sa"];class f extends HTMLElement{constructor(){super();m(this,"root");m(this,"abortController",null);m(this,"step","slots");m(this,"slots",[]);m(this,"selectedSlot",null);m(this,"selectedDate","");m(this,"selectedTime","");m(this,"availableTimes",[]);m(this,"isLoading",!1);m(this,"errorMsg","");m(this,"handleRootClick",e=>{const r=e.target.closest("[data-action]");r&&this.handleAction(r.dataset.action??"",r)});this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.abortController=new AbortController,this.root.addEventListener("click",this.handleRootClick,{signal:this.abortController.signal}),this.render(),this.loadSlots()}disconnectedCallback(){var e;(e=this.abortController)==null||e.abort(),this.abortController=null}attributeChangedCallback(e,t,r){t!==r&&(e==="theme"?this.render():this.isConnected&&this.reset())}get apiUrl(){return(this.getAttribute("api-url")??"").replace(/\/$/,"")}get profileSlug(){return this.getAttribute("profile-slug")??""}get vaultSlug(){return this.getAttribute("vault-slug")??""}get baseApiPath(){return`${this.apiUrl}/api/v1/public/vaults/${this.profileSlug}/${this.vaultSlug}`}reset(){this.step="slots",this.slots=[],this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.availableTimes=[],this.isLoading=!1,this.errorMsg="",this.render(),this.loadSlots()}setLoading(e){this.isLoading=e,this.render()}setError(e){this.errorMsg=e,this.isLoading=!1,this.render()}async loadSlots(){var e;this.setLoading(!0);try{const t=await fetch(`${this.baseApiPath}/booking-slots`,{signal:(e=this.abortController)==null?void 0:e.signal});if(!t.ok)throw new Error(`HTTP ${t.status}`);const r=await t.json();this.slots=r.slots,this.errorMsg=""}catch{this.setError("Failed to load booking slots. Please try again.");return}this.isLoading=!1,this.render()}async loadTimes(e,t){var r;this.setLoading(!0);try{const s=await fetch(`${this.baseApiPath}/booking-slots/${e}/availability?date=${t}`,{signal:(r=this.abortController)==null?void 0:r.signal});if(!s.ok)throw new Error(`HTTP ${s.status}`);const o=await s.json();this.availableTimes=o.times,this.errorMsg=""}catch{this.setError("Failed to load available times. Please try again.");return}this.isLoading=!1,this.step="time",this.render()}async submitBooking(e,t,r,s){var c;if(!this.selectedSlot||!this.selectedDate||!this.selectedTime)return;this.setLoading(!0);const o=new Date(`${this.selectedDate}T${this.selectedTime}`).toISOString(),a=this.selectedSlot.id,n={guestName:e,guestEmail:t,startAt:o};r&&(n.guestPhone=r),s&&(n.notes=s);try{const i=await fetch(`${this.baseApiPath}/booking-slots/${a}/book`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),signal:(c=this.abortController)==null?void 0:c.signal});if(!i.ok){const h=await i.json().catch(()=>({}));throw new Error(h.error??h.message??`HTTP ${i.status}`)}const l=await i.json();this.isLoading=!1,this.step="success",this.render(),this.dispatchEvent(new CustomEvent("lsv-booking-submitted",{bubbles:!0,composed:!0,detail:{bookingId:"",startAt:l.startAt,endAt:"",slotTitle:this.selectedSlot.title}}))}catch(i){const l=i instanceof Error?i.message:"Booking failed. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:l,step:"form"}})),this.setError(l)}}getNextDates(){const e=[],t=new Date;t.setHours(0,0,0,0);for(let r=0;r<14;r++){const s=new Date(t.getTime()+r*864e5),o=s.toISOString().split("T")[0],a=s.toLocaleDateString("en-US",{month:"short",day:"numeric"});e.push({date:o,label:a,dayOfWeek:s.getDay()})}return e}isDateAllowed(e){if(!this.selectedSlot)return!1;const t=g[e];return this.selectedSlot.daysOfWeek.includes(t)}formatTime(e){try{if(e.includes("T"))return new Date(e).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0});const[t,r]=e.split(":").map(Number),s=new Date;return s.setHours(t,r,0,0),s.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}catch{return e}}formatDate(e){try{return new Date(`${e}T12:00:00`).toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric"})}catch{return e}}render(){const e=document.createElement("style");e.textContent=d;const t=document.createElement("div");if(t.className="widget",this.step==="success")t.appendChild(this.renderSuccess());else if(t.appendChild(this.renderStepIndicator()),this.isLoading)t.appendChild(this.renderLoading());else if(this.errorMsg)t.appendChild(this.renderError());else switch(this.step){case"slots":t.appendChild(this.renderSlots());break;case"date":t.appendChild(this.renderDate());break;case"time":t.appendChild(this.renderTime());break;case"form":t.appendChild(this.renderForm());break}this.root.replaceChildren(e,t),this.attachEvents()}renderStepIndicator(){const e=["slots","date","time","form"],t=e.indexOf(this.step),r=document.createElement("div");return r.className="step-indicator",e.forEach((s,o)=>{const a=document.createElement("div");a.className=`step-dot${o===t?" active":o<t?" done":""}`,r.appendChild(a)}),r}renderLoading(){const e=document.createElement("div");e.className="loading";const t=document.createElement("div");t.className="spinner";const r=document.createElement("span");return r.textContent="Loading…",e.appendChild(t),e.appendChild(r),e}renderError(){const e=document.createDocumentFragment(),t=document.createElement("div");t.className="error-box",t.textContent=this.errorMsg,e.appendChild(t);const r=document.createElement("button");r.className="btn-retry",r.dataset.action="retry",r.textContent="Try again",e.appendChild(r);const s=document.createElement("div");return s.appendChild(e),s}renderSlots(){const e=document.createDocumentFragment(),t=document.createElement("p");if(t.className="section-title",t.textContent="Choose a booking type",e.appendChild(t),this.slots.length===0){const s=document.createElement("p");s.className="empty-msg",s.textContent="No booking slots available.",e.appendChild(s)}else{const s=document.createElement("div");s.className="slot-list",this.slots.forEach(o=>{const a=document.createElement("button");a.className="slot-card",a.dataset.action="select-slot",a.dataset.slotId=o.id;const n=document.createElement("p");n.className="slot-card-title",n.textContent=o.title;const c=document.createElement("p");c.className="slot-card-meta";const i=this.formatDayList(o.daysOfWeek);if(c.textContent=`${o.durationMin} min · ${i} · ${o.startTime}–${o.endTime}`,a.appendChild(n),o.description){const l=document.createElement("p");l.className="slot-card-meta",l.style.marginTop="0.25rem",l.textContent=o.description,a.appendChild(l)}a.appendChild(c),s.appendChild(a)}),e.appendChild(s)}const r=document.createElement("div");return r.appendChild(e),r}formatDayList(e){if(e.length===7)return"Every day";if(e.length===5&&!e.includes("sat")&&!e.includes("sun"))return"Weekdays";if(e.length===2&&e.includes("sat")&&e.includes("sun"))return"Weekends";const t={mon:"Mon",tue:"Tue",wed:"Wed",thu:"Thu",fri:"Fri",sat:"Sat",sun:"Sun"};return e.map(r=>t[r]??r).join(", ")}renderDate(){var c;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-slots",t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");r.className="section-title",r.textContent="Select a date",e.appendChild(r);const s=this.getNextDates(),o=document.createElement("div");o.className="date-grid",C.forEach(i=>{const l=document.createElement("div");l.className="date-header",l.textContent=i,o.appendChild(l)});const a=((c=s[0])==null?void 0:c.dayOfWeek)??0;for(let i=0;i<a;i++){const l=document.createElement("div");l.className="date-cell empty",o.appendChild(l)}s.forEach(({date:i,label:l,dayOfWeek:h})=>{const p=document.createElement("button"),b=this.isDateAllowed(h);p.className=`date-cell${b?"":" disabled"}${i===this.selectedDate?" selected":""}`,p.textContent=new Date(`${i}T12:00:00`).getDate().toString(),p.title=l,b?(p.dataset.action="select-date",p.dataset.date=i):p.disabled=!0,o.appendChild(p)}),e.appendChild(o);const n=document.createElement("div");return n.appendChild(e),n}renderTime(){const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-date",t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");if(r.className="section-title",r.textContent=`Available times · ${this.formatDate(this.selectedDate)}`,e.appendChild(r),this.availableTimes.length===0){const o=document.createElement("p");o.className="empty-msg",o.textContent="No times available on this date. Try another day.",e.appendChild(o)}else{const o=document.createElement("div");o.className="time-grid",this.availableTimes.forEach(a=>{const n=document.createElement("button");n.className=`time-btn${a===this.selectedTime?" selected":""}`,n.dataset.action="select-time",n.dataset.time=a,n.textContent=this.formatTime(a),o.appendChild(n)}),e.appendChild(o)}if(this.selectedTime){const o=document.createElement("div");o.className="actions";const a=document.createElement("button");a.className="btn-primary",a.dataset.action="go-to-form",a.textContent="Continue",o.appendChild(a),e.appendChild(o)}const s=document.createElement("div");return s.appendChild(e),s}renderForm(){var b,v;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-time",t.textContent="← Back",e.appendChild(t);const r=document.createElement("div");r.className="booking-summary";const s=document.createElement("strong");s.textContent=((b=this.selectedSlot)==null?void 0:b.title)??"",r.appendChild(s);const o=document.createElement("span");o.textContent=`${this.formatDate(this.selectedDate)} at ${this.formatTime(this.selectedTime)}`,r.appendChild(o),e.appendChild(r);const a=document.createElement("form");a.id="lsv-booking-form",a.appendChild(this.makeField("guestName","Full name","text",!0)),a.appendChild(this.makeField("guestEmail","Email address","email",!0)),(v=this.selectedSlot)!=null&&v.requirePhone&&a.appendChild(this.makeField("guestPhone","Phone number","tel",!0));const n=document.createElement("div");n.className="form-group";const c=document.createElement("label");c.className="form-label",c.setAttribute("for","lsv-notes"),c.textContent="Notes (optional)";const i=document.createElement("textarea");i.className="form-textarea",i.id="lsv-notes",i.name="notes",i.rows=3,i.placeholder="Anything you'd like to share…",n.appendChild(c),n.appendChild(i),a.appendChild(n);const l=document.createElement("div");l.className="actions";const h=document.createElement("button");h.type="submit",h.className="btn-primary",h.textContent="Confirm Booking",l.appendChild(h),a.appendChild(l),e.appendChild(a);const p=document.createElement("div");return p.appendChild(e),p}makeField(e,t,r,s){const o=document.createElement("div");o.className="form-group";const a=document.createElement("label");a.className="form-label",a.setAttribute("for",`lsv-${e}`),a.textContent=s?`${t} *`:t;const n=document.createElement("input");return n.className="form-input",n.type=r,n.id=`lsv-${e}`,n.name=e,n.required=s,n.autocomplete=e==="guestName"?"name":e==="guestEmail"?"email":e==="guestPhone"?"tel":"off",o.appendChild(a),o.appendChild(n),o}renderSuccess(){const e=document.createElement("div");e.className="success-view";const t=document.createElement("div");t.className="success-icon",t.textContent="✓";const r=document.createElement("p");r.className="success-title",r.textContent="Booking requested!";const s=document.createElement("p");return s.className="success-msg",s.textContent="Check your email to confirm your booking.",e.appendChild(t),e.appendChild(r),e.appendChild(s),e}attachEvents(){const e=this.root.querySelector("#lsv-booking-form");e&&e.addEventListener("submit",t=>{t.preventDefault();const r=new FormData(e),s=(r.get("guestName")??"").trim(),o=(r.get("guestEmail")??"").trim(),a=(r.get("guestPhone")??"").trim(),n=(r.get("notes")??"").trim();!s||!o||this.submitBooking(s,o,a,n)})}handleAction(e,t){switch(e){case"retry":this.errorMsg="",this.step==="slots"?this.loadSlots():this.step==="time"&&this.selectedSlot?this.loadTimes(this.selectedSlot.id,this.selectedDate):this.render();break;case"select-slot":{const r=t.dataset.slotId,s=this.slots.find(o=>o.id===r);if(!s)return;this.selectedSlot=s,this.step="date",this.render();break}case"back-to-slots":this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.step="slots",this.render();break;case"select-date":{const r=t.dataset.date;if(!r||!this.selectedSlot)return;this.selectedDate=r,this.selectedTime="",this.loadTimes(this.selectedSlot.id,r);break}case"back-to-date":this.selectedTime="",this.step="date",this.render();break;case"select-time":{const r=t.dataset.time;if(!r)return;this.selectedTime=r,this.render();break}case"go-to-form":this.selectedTime&&(this.step="form",this.render());break;case"back-to-time":this.step="time",this.render();break}}}m(f,"observedAttributes",["api-url","profile-slug","vault-slug","theme"]),customElements.get("lsv-booking")||customElements.define("lsv-booking",f),u.LsvBooking=f,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
|
393
|
+
`,b=["sun","mon","tue","wed","thu","fri","sat"],k=["Su","Mo","Tu","We","Th","Fr","Sa"];class f extends HTMLElement{constructor(){super();u(this,"root");u(this,"abortController",null);u(this,"step","slots");u(this,"slots",[]);u(this,"selectedSlot",null);u(this,"selectedDate","");u(this,"selectedTime","");u(this,"availableTimes",[]);u(this,"isLoading",!1);u(this,"errorMsg","");u(this,"handleRootClick",e=>{const r=e.target.closest("[data-action]");r&&this.handleAction(r.dataset.action??"",r)});this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.abortController=new AbortController,this.root.addEventListener("click",this.handleRootClick,{signal:this.abortController.signal}),this.render(),this.loadSlots()}disconnectedCallback(){var e;(e=this.abortController)==null||e.abort(),this.abortController=null}attributeChangedCallback(e,t,r){t!==r&&(e==="theme"?this.render():this.isConnected&&this.reset())}get apiUrl(){return(this.getAttribute("api-url")??"").replace(/\/$/,"")}get profileSlug(){return this.getAttribute("profile-slug")??""}get vaultSlug(){return this.getAttribute("vault-slug")??""}get baseApiPath(){return`${this.apiUrl}/api/v1/public/vaults/${this.profileSlug}/${this.vaultSlug}`}reset(){this.step="slots",this.slots=[],this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.availableTimes=[],this.isLoading=!1,this.errorMsg="",this.render(),this.loadSlots()}setLoading(e){this.isLoading=e,this.render()}setError(e){this.errorMsg=e,this.isLoading=!1,this.render()}getSignal(e=15e3){var r;const t=AbortSignal.timeout(e);return(r=this.abortController)!=null&&r.signal?AbortSignal.any([this.abortController.signal,t]):t}async loadSlots(){this.setLoading(!0);try{const e=await fetch(`${this.baseApiPath}/booking-slots`,{signal:this.getSignal()});if(!e.ok)throw new Error(`HTTP ${e.status}`);const t=await e.json();this.slots=t.slots,this.errorMsg=""}catch(e){const t=e instanceof Error?e.message:"Failed to load booking slots. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:t,step:"slots"}})),this.setError("Failed to load booking slots. Please try again.");return}this.isLoading=!1,this.render()}async loadTimes(e,t){this.setLoading(!0);try{const r=await fetch(`${this.baseApiPath}/booking-slots/${e}/availability?date=${t}`,{signal:this.getSignal()});if(!r.ok)throw new Error(`HTTP ${r.status}`);const o=await r.json();o.times.length>0&&typeof o.times[0]=="object"?this.availableTimes=o.times.map(s=>s.startAt):this.availableTimes=o.times,this.errorMsg=""}catch(r){const o=r instanceof Error?r.message:"Failed to load available times. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:o,step:"time"}})),this.setError("Failed to load available times. Please try again.");return}this.isLoading=!1,this.step="time",this.render()}async submitBooking(e,t,r,o){if(!this.selectedSlot||!this.selectedDate||!this.selectedTime)return;this.setLoading(!0);const s=this.selectedTime.includes("T")?this.selectedTime:new Date(`${this.selectedDate}T${this.selectedTime}`).toISOString(),a=this.selectedSlot.id,i={guestName:e,guestEmail:t,startAt:s};r&&(i.guestPhone=r),o&&(i.notes=o);try{const c=await fetch(`${this.baseApiPath}/booking-slots/${a}/book`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i),signal:this.getSignal()});if(!c.ok){const d=await c.json().catch(()=>({}));throw new Error(d.error??d.message??`HTTP ${c.status}`)}const n=await c.json();this.isLoading=!1,this.step="success",this.render();const l=new Date(n.startAt),h=new Date(l.getTime()+(this.selectedSlot.durationMin??0)*60*1e3);this.dispatchEvent(new CustomEvent("lsv-booking-submitted",{bubbles:!0,composed:!0,detail:{bookingId:"",startAt:n.startAt,endAt:h.toISOString(),slotTitle:this.selectedSlot.title}}))}catch(c){const n=c instanceof Error?c.message:"Booking failed. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:n,step:"form"}})),this.setError(n)}}getNextDates(){const e=[],t=new Date;t.setHours(0,0,0,0);for(let r=0;r<14;r++){const o=new Date(t.getTime()+r*864e5),s=o.toISOString().split("T")[0],a=o.toLocaleDateString("en-US",{month:"short",day:"numeric"});e.push({date:s,label:a,dayOfWeek:o.getDay()})}return e}isDateAllowed(e){if(!this.selectedSlot)return!1;const t=b[e];return this.selectedSlot.daysOfWeek.includes(t)}formatTime(e){try{if(e.includes("T"))return new Date(e).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0});const[t,r]=e.split(":").map(Number),o=new Date;return o.setHours(t,r,0,0),o.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}catch{return e}}formatDate(e){try{return new Date(`${e}T12:00:00`).toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric"})}catch{return e}}render(){const e=document.createElement("style");e.textContent=m;const t=document.createElement("div");if(t.className="widget",this.step==="success")t.appendChild(this.renderSuccess());else if(t.appendChild(this.renderStepIndicator()),this.isLoading)t.appendChild(this.renderLoading());else if(this.errorMsg)t.appendChild(this.renderError());else switch(this.step){case"slots":t.appendChild(this.renderSlots());break;case"date":t.appendChild(this.renderDate());break;case"time":t.appendChild(this.renderTime());break;case"form":t.appendChild(this.renderForm());break}this.root.replaceChildren(e,t),this.attachEvents()}renderStepIndicator(){const e=["slots","date","time","form"],t=e.indexOf(this.step),r=document.createElement("div");return r.className="step-indicator",r.setAttribute("aria-label","Booking progress"),r.setAttribute("role","navigation"),e.forEach((o,s)=>{const a=s===t,i=document.createElement("div");i.className=`step-dot${a?" active":s<t?" done":""}`,i.setAttribute("aria-label",`Step ${s+1}${a?" (current)":""}`),a&&i.setAttribute("aria-current","step"),r.appendChild(i)}),r}renderLoading(){const e=document.createElement("div");e.className="loading",e.setAttribute("aria-live","polite"),e.setAttribute("role","status");const t=document.createElement("div");t.className="spinner";const r=document.createElement("span");return r.textContent="Loading…",e.appendChild(t),e.appendChild(r),e}renderError(){const e=document.createDocumentFragment(),t=document.createElement("div");t.className="error-box",t.setAttribute("aria-live","assertive"),t.setAttribute("role","alert"),t.textContent=this.errorMsg,e.appendChild(t);const r=document.createElement("button");r.className="btn-retry",r.dataset.action="retry",r.textContent="Try again",e.appendChild(r);const o=document.createElement("div");return o.appendChild(e),o}renderSlots(){const e=document.createDocumentFragment(),t=document.createElement("p");if(t.className="section-title",t.textContent="Choose a booking type",e.appendChild(t),this.slots.length===0){const o=document.createElement("p");o.className="empty-msg",o.textContent="No booking slots available.",e.appendChild(o)}else{const o=document.createElement("div");o.className="slot-list",this.slots.forEach(s=>{const a=document.createElement("button");a.className="slot-card",a.dataset.action="select-slot",a.dataset.slotId=s.id;const i=document.createElement("p");i.className="slot-card-title",i.textContent=s.title;const c=document.createElement("p");c.className="slot-card-meta";const n=this.formatDayList(s.daysOfWeek);if(c.textContent=`${s.durationMin} min · ${n} · ${s.startTime}–${s.endTime}`,a.appendChild(i),s.description){const l=document.createElement("p");l.className="slot-card-meta",l.style.marginTop="0.25rem",l.textContent=s.description,a.appendChild(l)}a.appendChild(c),o.appendChild(a)}),e.appendChild(o)}const r=document.createElement("div");return r.appendChild(e),r}formatDayList(e){if(e.length===7)return"Every day";if(e.length===5&&!e.includes("sat")&&!e.includes("sun"))return"Weekdays";if(e.length===2&&e.includes("sat")&&e.includes("sun"))return"Weekends";const t={mon:"Mon",tue:"Tue",wed:"Wed",thu:"Thu",fri:"Fri",sat:"Sat",sun:"Sun"};return e.map(r=>t[r]??r).join(", ")}renderDate(){var c;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-slots",t.setAttribute("aria-label","Go back"),t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");r.className="section-title",r.textContent="Select a date",e.appendChild(r);const o=this.getNextDates(),s=document.createElement("div");s.className="date-grid",k.forEach(n=>{const l=document.createElement("div");l.className="date-header",l.textContent=n,s.appendChild(l)});const a=((c=o[0])==null?void 0:c.dayOfWeek)??0;for(let n=0;n<a;n++){const l=document.createElement("div");l.className="date-cell empty",s.appendChild(l)}o.forEach(({date:n,label:l,dayOfWeek:h})=>{const d=document.createElement("button"),g=this.isDateAllowed(h);d.className=`date-cell${g?"":" disabled"}${n===this.selectedDate?" selected":""}`,d.textContent=new Date(`${n}T12:00:00`).getDate().toString(),d.title=l,d.setAttribute("aria-label",`Select ${l}`),g?(d.dataset.action="select-date",d.dataset.date=n):d.disabled=!0,s.appendChild(d)}),e.appendChild(s);const i=document.createElement("div");return i.appendChild(e),i}renderTime(){const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-date",t.setAttribute("aria-label","Go back"),t.textContent="← Back",e.appendChild(t);const r=document.createElement("p");if(r.className="section-title",r.textContent=`Available times · ${this.formatDate(this.selectedDate)}`,e.appendChild(r),this.availableTimes.length===0){const s=document.createElement("p");s.className="empty-msg",s.textContent="No times available on this date. Try another day.",e.appendChild(s)}else{const s=document.createElement("div");s.className="time-grid",this.availableTimes.forEach(a=>{const i=document.createElement("button");i.className=`time-btn${a===this.selectedTime?" selected":""}`,i.dataset.action="select-time",i.dataset.time=a,i.textContent=this.formatTime(a),a===this.selectedTime&&i.setAttribute("aria-pressed","true"),s.appendChild(i)}),e.appendChild(s)}if(this.selectedTime){const s=document.createElement("div");s.className="actions";const a=document.createElement("button");a.className="btn-primary",a.dataset.action="go-to-form",a.textContent="Continue",s.appendChild(a),e.appendChild(s)}const o=document.createElement("div");return o.appendChild(e),o}renderForm(){var g,v;const e=document.createDocumentFragment(),t=document.createElement("button");t.className="btn-back",t.dataset.action="back-to-time",t.setAttribute("aria-label","Go back"),t.textContent="← Back",e.appendChild(t);const r=document.createElement("div");r.className="booking-summary";const o=document.createElement("strong");o.textContent=((g=this.selectedSlot)==null?void 0:g.title)??"",r.appendChild(o);const s=document.createElement("span");s.textContent=`${this.formatDate(this.selectedDate)} at ${this.formatTime(this.selectedTime)}`,r.appendChild(s),e.appendChild(r);const a=document.createElement("form");a.id="lsv-booking-form",a.appendChild(this.makeField("guestName","Full name","text",!0)),a.appendChild(this.makeField("guestEmail","Email address","email",!0)),(v=this.selectedSlot)!=null&&v.requirePhone&&a.appendChild(this.makeField("guestPhone","Phone number","tel",!0));const i=document.createElement("div");i.className="form-group";const c=document.createElement("label");c.className="form-label",c.setAttribute("for","lsv-notes"),c.textContent="Notes (optional)";const n=document.createElement("textarea");n.className="form-textarea",n.id="lsv-notes",n.name="notes",n.rows=3,n.placeholder="Anything you'd like to share…",i.appendChild(c),i.appendChild(n),a.appendChild(i);const l=document.createElement("div");l.className="actions";const h=document.createElement("button");h.type="submit",h.className="btn-primary",h.textContent="Confirm Booking",l.appendChild(h),a.appendChild(l),e.appendChild(a);const d=document.createElement("div");return d.appendChild(e),d}makeField(e,t,r,o){const s=document.createElement("div");s.className="form-group";const a=document.createElement("label");a.className="form-label",a.setAttribute("for",`lsv-${e}`),a.textContent=o?`${t} *`:t;const i=document.createElement("input");return i.className="form-input",i.type=r,i.id=`lsv-${e}`,i.name=e,i.required=o,i.autocomplete=e==="guestName"?"name":e==="guestEmail"?"email":e==="guestPhone"?"tel":"off",s.appendChild(a),s.appendChild(i),s}renderSuccess(){const e=document.createElement("div");e.className="success-view";const t=document.createElement("div");t.className="success-icon",t.textContent="✓";const r=document.createElement("p");r.className="success-title",r.textContent="Booking requested!";const o=document.createElement("p");return o.className="success-msg",o.textContent="Check your email to confirm your booking.",e.appendChild(t),e.appendChild(r),e.appendChild(o),e}attachEvents(){const e=this.root.querySelector("#lsv-booking-form");e&&e.addEventListener("submit",t=>{t.preventDefault();const r=new FormData(e),o=(r.get("guestName")??"").trim(),s=(r.get("guestEmail")??"").trim(),a=(r.get("guestPhone")??"").trim(),i=(r.get("notes")??"").trim();!o||!s||this.submitBooking(o,s,a,i)})}handleAction(e,t){switch(e){case"retry":this.errorMsg="",this.step==="slots"?this.loadSlots():this.step==="time"&&this.selectedSlot?this.loadTimes(this.selectedSlot.id,this.selectedDate):this.render();break;case"select-slot":{const r=t.dataset.slotId,o=this.slots.find(s=>s.id===r);if(!o)return;this.selectedSlot=o,this.step="date",this.render();break}case"back-to-slots":this.selectedSlot=null,this.selectedDate="",this.selectedTime="",this.step="slots",this.render();break;case"select-date":{const r=t.dataset.date;if(!r||!this.selectedSlot)return;this.selectedDate=r,this.selectedTime="",this.loadTimes(this.selectedSlot.id,r);break}case"back-to-date":this.selectedTime="",this.step="date",this.render();break;case"select-time":{const r=t.dataset.time;if(!r)return;this.selectedTime=r,this.render();break}case"go-to-form":this.selectedTime&&(this.step="form",this.render());break;case"back-to-time":this.step="time",this.render();break}}}u(f,"observedAttributes",["api-url","profile-slug","vault-slug","theme"]),customElements.get("lsv-booking")||customElements.define("lsv-booking",f),p.LsvBooking=f,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifestreamdynamics/booking-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Embeddable booking widget Web Component for Lifestream Vault",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=22"
|
|
7
|
+
},
|
|
5
8
|
"type": "module",
|
|
6
9
|
"main": "dist/lsv-booking.umd.cjs",
|
|
7
10
|
"module": "dist/lsv-booking.js",
|