@lifestreamdynamics/booking-widget 0.1.0 → 0.1.1
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 +3 -0
- package/dist/lsv-booking.js +100 -92
- package/dist/lsv-booking.umd.cjs +2 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare class LsvBooking extends HTMLElement {
|
|
2
2
|
static observedAttributes: string[];
|
|
3
3
|
private root;
|
|
4
|
+
private abortController;
|
|
4
5
|
private step;
|
|
5
6
|
private slots;
|
|
6
7
|
private selectedSlot;
|
|
@@ -11,6 +12,7 @@ export declare class LsvBooking extends HTMLElement {
|
|
|
11
12
|
private errorMsg;
|
|
12
13
|
constructor();
|
|
13
14
|
connectedCallback(): void;
|
|
15
|
+
disconnectedCallback(): void;
|
|
14
16
|
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
|
|
15
17
|
private get apiUrl();
|
|
16
18
|
private get profileSlug();
|
|
@@ -37,6 +39,7 @@ export declare class LsvBooking extends HTMLElement {
|
|
|
37
39
|
private renderForm;
|
|
38
40
|
private makeField;
|
|
39
41
|
private renderSuccess;
|
|
42
|
+
private handleRootClick;
|
|
40
43
|
private attachEvents;
|
|
41
44
|
private handleAction;
|
|
42
45
|
}
|
package/dist/lsv-booking.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var v = Object.defineProperty;
|
|
2
|
-
var
|
|
3
|
-
var d = (
|
|
2
|
+
var C = (h, p, e) => p in h ? v(h, p, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[p] = e;
|
|
3
|
+
var d = (h, p, e) => C(h, typeof p != "symbol" ? p + "" : p, e);
|
|
4
4
|
const k = `
|
|
5
5
|
:host {
|
|
6
6
|
display: block;
|
|
@@ -393,11 +393,12 @@ const k = `
|
|
|
393
393
|
gap: 0.5rem;
|
|
394
394
|
margin-top: 1rem;
|
|
395
395
|
}
|
|
396
|
-
`,
|
|
396
|
+
`, y = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"], x = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
397
397
|
class f extends HTMLElement {
|
|
398
398
|
constructor() {
|
|
399
399
|
super();
|
|
400
400
|
d(this, "root");
|
|
401
|
+
d(this, "abortController", null);
|
|
401
402
|
d(this, "step", "slots");
|
|
402
403
|
d(this, "slots", []);
|
|
403
404
|
d(this, "selectedSlot", null);
|
|
@@ -406,10 +407,19 @@ class f extends HTMLElement {
|
|
|
406
407
|
d(this, "availableTimes", []);
|
|
407
408
|
d(this, "isLoading", !1);
|
|
408
409
|
d(this, "errorMsg", "");
|
|
410
|
+
// ── Event handling ────────────────────────────────────────────────────────
|
|
411
|
+
d(this, "handleRootClick", (e) => {
|
|
412
|
+
const r = e.target.closest("[data-action]");
|
|
413
|
+
r && this.handleAction(r.dataset.action ?? "", r);
|
|
414
|
+
});
|
|
409
415
|
this.root = this.attachShadow({ mode: "open" });
|
|
410
416
|
}
|
|
411
417
|
connectedCallback() {
|
|
412
|
-
this.render(), this.loadSlots();
|
|
418
|
+
this.abortController = new AbortController(), this.root.addEventListener("click", this.handleRootClick, { signal: this.abortController.signal }), this.render(), this.loadSlots();
|
|
419
|
+
}
|
|
420
|
+
disconnectedCallback() {
|
|
421
|
+
var e;
|
|
422
|
+
(e = this.abortController) == null || e.abort(), this.abortController = null;
|
|
413
423
|
}
|
|
414
424
|
attributeChangedCallback(e, t, r) {
|
|
415
425
|
t !== r && (e === "theme" ? this.render() : this.isConnected && this.reset());
|
|
@@ -439,12 +449,13 @@ class f extends HTMLElement {
|
|
|
439
449
|
}
|
|
440
450
|
// ── API calls ─────────────────────────────────────────────────────────────
|
|
441
451
|
async loadSlots() {
|
|
452
|
+
var e;
|
|
442
453
|
this.setLoading(!0);
|
|
443
454
|
try {
|
|
444
|
-
const
|
|
445
|
-
if (!
|
|
446
|
-
const
|
|
447
|
-
this.slots =
|
|
455
|
+
const t = await fetch(`${this.baseApiPath}/booking-slots`, { signal: (e = this.abortController) == null ? void 0 : e.signal });
|
|
456
|
+
if (!t.ok) throw new Error(`HTTP ${t.status}`);
|
|
457
|
+
const r = await t.json();
|
|
458
|
+
this.slots = r.slots, this.errorMsg = "";
|
|
448
459
|
} catch {
|
|
449
460
|
this.setError("Failed to load booking slots. Please try again.");
|
|
450
461
|
return;
|
|
@@ -452,13 +463,15 @@ class f extends HTMLElement {
|
|
|
452
463
|
this.isLoading = !1, this.render();
|
|
453
464
|
}
|
|
454
465
|
async loadTimes(e, t) {
|
|
466
|
+
var r;
|
|
455
467
|
this.setLoading(!0);
|
|
456
468
|
try {
|
|
457
|
-
const
|
|
458
|
-
`${this.baseApiPath}/booking-slots/${e}/availability?date=${t}
|
|
469
|
+
const s = await fetch(
|
|
470
|
+
`${this.baseApiPath}/booking-slots/${e}/availability?date=${t}`,
|
|
471
|
+
{ signal: (r = this.abortController) == null ? void 0 : r.signal }
|
|
459
472
|
);
|
|
460
|
-
if (!
|
|
461
|
-
const o = await
|
|
473
|
+
if (!s.ok) throw new Error(`HTTP ${s.status}`);
|
|
474
|
+
const o = await s.json();
|
|
462
475
|
this.availableTimes = o.times, this.errorMsg = "";
|
|
463
476
|
} catch {
|
|
464
477
|
this.setError("Failed to load available times. Please try again.");
|
|
@@ -466,46 +479,48 @@ class f extends HTMLElement {
|
|
|
466
479
|
}
|
|
467
480
|
this.isLoading = !1, this.step = "time", this.render();
|
|
468
481
|
}
|
|
469
|
-
async submitBooking(e, t, r,
|
|
482
|
+
async submitBooking(e, t, r, s) {
|
|
483
|
+
var c;
|
|
470
484
|
if (!this.selectedSlot || !this.selectedDate || !this.selectedTime) return;
|
|
471
485
|
this.setLoading(!0);
|
|
472
|
-
const
|
|
486
|
+
const o = (/* @__PURE__ */ new Date(`${this.selectedDate}T${this.selectedTime}`)).toISOString(), a = this.selectedSlot.id, n = {
|
|
473
487
|
guestName: e,
|
|
474
488
|
guestEmail: t,
|
|
475
|
-
startAt:
|
|
489
|
+
startAt: o
|
|
476
490
|
};
|
|
477
|
-
r && (n.guestPhone = r),
|
|
491
|
+
r && (n.guestPhone = r), s && (n.notes = s);
|
|
478
492
|
try {
|
|
479
|
-
const
|
|
493
|
+
const i = await fetch(
|
|
480
494
|
`${this.baseApiPath}/booking-slots/${a}/book`,
|
|
481
495
|
{
|
|
482
496
|
method: "POST",
|
|
483
497
|
headers: { "Content-Type": "application/json" },
|
|
484
|
-
body: JSON.stringify(n)
|
|
498
|
+
body: JSON.stringify(n),
|
|
499
|
+
signal: (c = this.abortController) == null ? void 0 : c.signal
|
|
485
500
|
}
|
|
486
501
|
);
|
|
487
|
-
if (!
|
|
488
|
-
const
|
|
489
|
-
throw new Error(
|
|
502
|
+
if (!i.ok) {
|
|
503
|
+
const u = await i.json().catch(() => ({}));
|
|
504
|
+
throw new Error(u.error ?? u.message ?? `HTTP ${i.status}`);
|
|
490
505
|
}
|
|
491
|
-
const
|
|
506
|
+
const l = await i.json();
|
|
492
507
|
this.isLoading = !1, this.step = "success", this.render(), this.dispatchEvent(new CustomEvent("lsv-booking-submitted", {
|
|
493
508
|
bubbles: !0,
|
|
494
509
|
composed: !0,
|
|
495
510
|
detail: {
|
|
496
511
|
bookingId: "",
|
|
497
|
-
startAt:
|
|
512
|
+
startAt: l.startAt,
|
|
498
513
|
endAt: "",
|
|
499
514
|
slotTitle: this.selectedSlot.title
|
|
500
515
|
}
|
|
501
516
|
}));
|
|
502
|
-
} catch (
|
|
503
|
-
const
|
|
517
|
+
} catch (i) {
|
|
518
|
+
const l = i instanceof Error ? i.message : "Booking failed. Please try again.";
|
|
504
519
|
this.dispatchEvent(new CustomEvent("lsv-booking-error", {
|
|
505
520
|
bubbles: !0,
|
|
506
521
|
composed: !0,
|
|
507
|
-
detail: { message:
|
|
508
|
-
})), this.setError(
|
|
522
|
+
detail: { message: l, step: "form" }
|
|
523
|
+
})), this.setError(l);
|
|
509
524
|
}
|
|
510
525
|
}
|
|
511
526
|
// ── Date helpers ──────────────────────────────────────────────────────────
|
|
@@ -513,16 +528,14 @@ class f extends HTMLElement {
|
|
|
513
528
|
const e = [], t = /* @__PURE__ */ new Date();
|
|
514
529
|
t.setHours(0, 0, 0, 0);
|
|
515
530
|
for (let r = 0; r < 14; r++) {
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
const s = o.toISOString().split("T")[0], a = o.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
519
|
-
e.push({ date: s, label: a, dayOfWeek: o.getDay() });
|
|
531
|
+
const s = new Date(t.getTime() + r * 864e5), o = s.toISOString().split("T")[0], a = s.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
532
|
+
e.push({ date: o, label: a, dayOfWeek: s.getDay() });
|
|
520
533
|
}
|
|
521
534
|
return e;
|
|
522
535
|
}
|
|
523
536
|
isDateAllowed(e) {
|
|
524
537
|
if (!this.selectedSlot) return !1;
|
|
525
|
-
const t =
|
|
538
|
+
const t = y[e];
|
|
526
539
|
return this.selectedSlot.daysOfWeek.includes(t);
|
|
527
540
|
}
|
|
528
541
|
formatTime(e) {
|
|
@@ -533,8 +546,8 @@ class f extends HTMLElement {
|
|
|
533
546
|
minute: "2-digit",
|
|
534
547
|
hour12: !0
|
|
535
548
|
});
|
|
536
|
-
const [t, r] = e.split(":").map(Number),
|
|
537
|
-
return
|
|
549
|
+
const [t, r] = e.split(":").map(Number), s = /* @__PURE__ */ new Date();
|
|
550
|
+
return s.setHours(t, r, 0, 0), s.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: !0 });
|
|
538
551
|
} catch {
|
|
539
552
|
return e;
|
|
540
553
|
}
|
|
@@ -576,9 +589,9 @@ class f extends HTMLElement {
|
|
|
576
589
|
}
|
|
577
590
|
renderStepIndicator() {
|
|
578
591
|
const e = ["slots", "date", "time", "form"], t = e.indexOf(this.step), r = document.createElement("div");
|
|
579
|
-
return r.className = "step-indicator", e.forEach((
|
|
592
|
+
return r.className = "step-indicator", e.forEach((s, o) => {
|
|
580
593
|
const a = document.createElement("div");
|
|
581
|
-
a.className = `step-dot${
|
|
594
|
+
a.className = `step-dot${o === t ? " active" : o < t ? " done" : ""}`, r.appendChild(a);
|
|
582
595
|
}), r;
|
|
583
596
|
}
|
|
584
597
|
renderLoading() {
|
|
@@ -594,30 +607,30 @@ class f extends HTMLElement {
|
|
|
594
607
|
t.className = "error-box", t.textContent = this.errorMsg, e.appendChild(t);
|
|
595
608
|
const r = document.createElement("button");
|
|
596
609
|
r.className = "btn-retry", r.dataset.action = "retry", r.textContent = "Try again", e.appendChild(r);
|
|
597
|
-
const
|
|
598
|
-
return
|
|
610
|
+
const s = document.createElement("div");
|
|
611
|
+
return s.appendChild(e), s;
|
|
599
612
|
}
|
|
600
613
|
renderSlots() {
|
|
601
614
|
const e = document.createDocumentFragment(), t = document.createElement("p");
|
|
602
615
|
if (t.className = "section-title", t.textContent = "Choose a booking type", e.appendChild(t), this.slots.length === 0) {
|
|
603
|
-
const
|
|
604
|
-
|
|
616
|
+
const s = document.createElement("p");
|
|
617
|
+
s.className = "empty-msg", s.textContent = "No booking slots available.", e.appendChild(s);
|
|
605
618
|
} else {
|
|
606
|
-
const
|
|
607
|
-
|
|
619
|
+
const s = document.createElement("div");
|
|
620
|
+
s.className = "slot-list", this.slots.forEach((o) => {
|
|
608
621
|
const a = document.createElement("button");
|
|
609
|
-
a.className = "slot-card", a.dataset.action = "select-slot", a.dataset.slotId =
|
|
622
|
+
a.className = "slot-card", a.dataset.action = "select-slot", a.dataset.slotId = o.id;
|
|
610
623
|
const n = document.createElement("p");
|
|
611
|
-
n.className = "slot-card-title", n.textContent =
|
|
624
|
+
n.className = "slot-card-title", n.textContent = o.title;
|
|
612
625
|
const c = document.createElement("p");
|
|
613
626
|
c.className = "slot-card-meta";
|
|
614
|
-
const i = this.formatDayList(
|
|
615
|
-
if (c.textContent = `${
|
|
627
|
+
const i = this.formatDayList(o.daysOfWeek);
|
|
628
|
+
if (c.textContent = `${o.durationMin} min · ${i} · ${o.startTime}–${o.endTime}`, a.appendChild(n), o.description) {
|
|
616
629
|
const l = document.createElement("p");
|
|
617
|
-
l.className = "slot-card-meta", l.style.marginTop = "0.25rem", l.textContent =
|
|
630
|
+
l.className = "slot-card-meta", l.style.marginTop = "0.25rem", l.textContent = o.description, a.appendChild(l);
|
|
618
631
|
}
|
|
619
|
-
a.appendChild(c),
|
|
620
|
-
}), e.appendChild(
|
|
632
|
+
a.appendChild(c), s.appendChild(a);
|
|
633
|
+
}), e.appendChild(s);
|
|
621
634
|
}
|
|
622
635
|
const r = document.createElement("div");
|
|
623
636
|
return r.appendChild(e), r;
|
|
@@ -643,20 +656,20 @@ class f extends HTMLElement {
|
|
|
643
656
|
t.className = "btn-back", t.dataset.action = "back-to-slots", t.textContent = "← Back", e.appendChild(t);
|
|
644
657
|
const r = document.createElement("p");
|
|
645
658
|
r.className = "section-title", r.textContent = "Select a date", e.appendChild(r);
|
|
646
|
-
const
|
|
647
|
-
|
|
659
|
+
const s = this.getNextDates(), o = document.createElement("div");
|
|
660
|
+
o.className = "date-grid", x.forEach((i) => {
|
|
648
661
|
const l = document.createElement("div");
|
|
649
|
-
l.className = "date-header", l.textContent = i,
|
|
662
|
+
l.className = "date-header", l.textContent = i, o.appendChild(l);
|
|
650
663
|
});
|
|
651
|
-
const a = ((c =
|
|
664
|
+
const a = ((c = s[0]) == null ? void 0 : c.dayOfWeek) ?? 0;
|
|
652
665
|
for (let i = 0; i < a; i++) {
|
|
653
666
|
const l = document.createElement("div");
|
|
654
|
-
l.className = "date-cell empty",
|
|
667
|
+
l.className = "date-cell empty", o.appendChild(l);
|
|
655
668
|
}
|
|
656
|
-
|
|
657
|
-
const m = document.createElement("button"), g = this.isDateAllowed(
|
|
658
|
-
m.className = `date-cell${g ? "" : " disabled"}${i === this.selectedDate ? " selected" : ""}`, m.textContent = (/* @__PURE__ */ new Date(`${i}T12:00:00`)).getDate().toString(), m.title = l, g ? (m.dataset.action = "select-date", m.dataset.date = i) : m.disabled = !0,
|
|
659
|
-
}), e.appendChild(
|
|
669
|
+
s.forEach(({ date: i, label: l, dayOfWeek: u }) => {
|
|
670
|
+
const m = document.createElement("button"), g = this.isDateAllowed(u);
|
|
671
|
+
m.className = `date-cell${g ? "" : " disabled"}${i === this.selectedDate ? " selected" : ""}`, m.textContent = (/* @__PURE__ */ new Date(`${i}T12:00:00`)).getDate().toString(), m.title = l, g ? (m.dataset.action = "select-date", m.dataset.date = i) : m.disabled = !0, o.appendChild(m);
|
|
672
|
+
}), e.appendChild(o);
|
|
660
673
|
const n = document.createElement("div");
|
|
661
674
|
return n.appendChild(e), n;
|
|
662
675
|
}
|
|
@@ -665,23 +678,23 @@ class f extends HTMLElement {
|
|
|
665
678
|
t.className = "btn-back", t.dataset.action = "back-to-date", t.textContent = "← Back", e.appendChild(t);
|
|
666
679
|
const r = document.createElement("p");
|
|
667
680
|
if (r.className = "section-title", r.textContent = `Available times · ${this.formatDate(this.selectedDate)}`, e.appendChild(r), this.availableTimes.length === 0) {
|
|
668
|
-
const
|
|
669
|
-
|
|
681
|
+
const o = document.createElement("p");
|
|
682
|
+
o.className = "empty-msg", o.textContent = "No times available on this date. Try another day.", e.appendChild(o);
|
|
670
683
|
} else {
|
|
671
|
-
const
|
|
672
|
-
|
|
684
|
+
const o = document.createElement("div");
|
|
685
|
+
o.className = "time-grid", this.availableTimes.forEach((a) => {
|
|
673
686
|
const n = document.createElement("button");
|
|
674
|
-
n.className = `time-btn${a === this.selectedTime ? " selected" : ""}`, n.dataset.action = "select-time", n.dataset.time = a, n.textContent = this.formatTime(a),
|
|
675
|
-
}), e.appendChild(
|
|
687
|
+
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);
|
|
688
|
+
}), e.appendChild(o);
|
|
676
689
|
}
|
|
677
690
|
if (this.selectedTime) {
|
|
678
|
-
const
|
|
679
|
-
|
|
691
|
+
const o = document.createElement("div");
|
|
692
|
+
o.className = "actions";
|
|
680
693
|
const a = document.createElement("button");
|
|
681
|
-
a.className = "btn-primary", a.dataset.action = "go-to-form", a.textContent = "Continue",
|
|
694
|
+
a.className = "btn-primary", a.dataset.action = "go-to-form", a.textContent = "Continue", o.appendChild(a), e.appendChild(o);
|
|
682
695
|
}
|
|
683
|
-
const
|
|
684
|
-
return
|
|
696
|
+
const s = document.createElement("div");
|
|
697
|
+
return s.appendChild(e), s;
|
|
685
698
|
}
|
|
686
699
|
renderForm() {
|
|
687
700
|
var g, b;
|
|
@@ -689,10 +702,10 @@ class f extends HTMLElement {
|
|
|
689
702
|
t.className = "btn-back", t.dataset.action = "back-to-time", t.textContent = "← Back", e.appendChild(t);
|
|
690
703
|
const r = document.createElement("div");
|
|
691
704
|
r.className = "booking-summary";
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
|
|
705
|
+
const s = document.createElement("strong");
|
|
706
|
+
s.textContent = ((g = this.selectedSlot) == null ? void 0 : g.title) ?? "", r.appendChild(s);
|
|
707
|
+
const o = document.createElement("span");
|
|
708
|
+
o.textContent = `${this.formatDate(this.selectedDate)} at ${this.formatTime(this.selectedTime)}`, r.appendChild(o), e.appendChild(r);
|
|
696
709
|
const a = document.createElement("form");
|
|
697
710
|
a.id = "lsv-booking-form", a.appendChild(this.makeField("guestName", "Full name", "text", !0)), a.appendChild(this.makeField("guestEmail", "Email address", "email", !0)), (b = this.selectedSlot) != null && b.requirePhone && a.appendChild(this.makeField("guestPhone", "Phone number", "tel", !0));
|
|
698
711
|
const n = document.createElement("div");
|
|
@@ -703,18 +716,18 @@ class f extends HTMLElement {
|
|
|
703
716
|
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);
|
|
704
717
|
const l = document.createElement("div");
|
|
705
718
|
l.className = "actions";
|
|
706
|
-
const
|
|
707
|
-
|
|
719
|
+
const u = document.createElement("button");
|
|
720
|
+
u.type = "submit", u.className = "btn-primary", u.textContent = "Confirm Booking", l.appendChild(u), a.appendChild(l), e.appendChild(a);
|
|
708
721
|
const m = document.createElement("div");
|
|
709
722
|
return m.appendChild(e), m;
|
|
710
723
|
}
|
|
711
|
-
makeField(e, t, r,
|
|
712
|
-
const
|
|
713
|
-
|
|
724
|
+
makeField(e, t, r, s) {
|
|
725
|
+
const o = document.createElement("div");
|
|
726
|
+
o.className = "form-group";
|
|
714
727
|
const a = document.createElement("label");
|
|
715
|
-
a.className = "form-label", a.setAttribute("for", `lsv-${e}`), a.textContent =
|
|
728
|
+
a.className = "form-label", a.setAttribute("for", `lsv-${e}`), a.textContent = s ? `${t} *` : t;
|
|
716
729
|
const n = document.createElement("input");
|
|
717
|
-
return n.className = "form-input", n.type = r, n.id = `lsv-${e}`, n.name = e, n.required =
|
|
730
|
+
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;
|
|
718
731
|
}
|
|
719
732
|
renderSuccess() {
|
|
720
733
|
const e = document.createElement("div");
|
|
@@ -723,20 +736,15 @@ class f extends HTMLElement {
|
|
|
723
736
|
t.className = "success-icon", t.textContent = "✓";
|
|
724
737
|
const r = document.createElement("p");
|
|
725
738
|
r.className = "success-title", r.textContent = "Booking requested!";
|
|
726
|
-
const
|
|
727
|
-
return
|
|
739
|
+
const s = document.createElement("p");
|
|
740
|
+
return s.className = "success-msg", s.textContent = "Check your email to confirm your booking.", e.appendChild(t), e.appendChild(r), e.appendChild(s), e;
|
|
728
741
|
}
|
|
729
|
-
// ── Event handling ────────────────────────────────────────────────────────
|
|
730
742
|
attachEvents() {
|
|
731
|
-
this.root.addEventListener("click", (t) => {
|
|
732
|
-
const o = t.target.closest("[data-action]");
|
|
733
|
-
o && this.handleAction(o.dataset.action ?? "", o);
|
|
734
|
-
});
|
|
735
743
|
const e = this.root.querySelector("#lsv-booking-form");
|
|
736
744
|
e && e.addEventListener("submit", (t) => {
|
|
737
745
|
t.preventDefault();
|
|
738
|
-
const r = new FormData(e),
|
|
739
|
-
!
|
|
746
|
+
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();
|
|
747
|
+
!s || !o || this.submitBooking(s, o, a, n);
|
|
740
748
|
});
|
|
741
749
|
}
|
|
742
750
|
handleAction(e, t) {
|
|
@@ -745,9 +753,9 @@ class f extends HTMLElement {
|
|
|
745
753
|
this.errorMsg = "", this.step === "slots" ? this.loadSlots() : this.step === "time" && this.selectedSlot ? this.loadTimes(this.selectedSlot.id, this.selectedDate) : this.render();
|
|
746
754
|
break;
|
|
747
755
|
case "select-slot": {
|
|
748
|
-
const r = t.dataset.slotId,
|
|
749
|
-
if (!
|
|
750
|
-
this.selectedSlot =
|
|
756
|
+
const r = t.dataset.slotId, s = this.slots.find((o) => o.id === r);
|
|
757
|
+
if (!s) return;
|
|
758
|
+
this.selectedSlot = s, this.step = "date", this.render();
|
|
751
759
|
break;
|
|
752
760
|
}
|
|
753
761
|
case "back-to-slots":
|
package/dist/lsv-booking.umd.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(u,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(u=typeof globalThis<"u"?globalThis:u||self,d(u.LsvBooking={}))})(this,(function(u){"use strict";var y=Object.defineProperty;var k=(u,d,g)=>d in u?y(u,d,{enumerable:!0,configurable:!0,writable:!0,value:g}):u[d]=g;var m=(u,d,g)=>k(u,typeof d!="symbol"?d+"":d,g);const d=`
|
|
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
|
-
`,h=["sun","mon","tue","wed","thu","fri","sat"],y=["Su","Mo","Tu","We","Th","Fr","Sa"];class f extends HTMLElement{constructor(){super();u(this,"root");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","");this.root=this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.loadSlots()}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(){this.setLoading(!0);try{const e=await fetch(`${this.baseApiPath}/booking-slots`);if(!e.ok)throw new Error(`HTTP ${e.status}`);const t=await e.json();this.slots=t.slots,this.errorMsg=""}catch{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}`);if(!r.ok)throw new Error(`HTTP ${r.status}`);const o=await r.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,o){if(!this.selectedSlot||!this.selectedDate||!this.selectedTime)return;this.setLoading(!0);const s=new Date(`${this.selectedDate}T${this.selectedTime}`).toISOString(),a=this.selectedSlot.id,n={guestName:e,guestEmail:t,startAt:s};r&&(n.guestPhone=r),o&&(n.notes=o);try{const c=await fetch(`${this.baseApiPath}/booking-slots/${a}/book`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!c.ok){const l=await c.json().catch(()=>({}));throw new Error(l.error??l.message??`HTTP ${c.status}`)}const i=await c.json();this.isLoading=!1,this.step="success",this.render(),this.dispatchEvent(new CustomEvent("lsv-booking-submitted",{bubbles:!0,composed:!0,detail:{bookingId:"",startAt:i.startAt,endAt:"",slotTitle:this.selectedSlot.title}}))}catch(c){const i=c instanceof Error?c.message:"Booking failed. Please try again.";this.dispatchEvent(new CustomEvent("lsv-booking-error",{bubbles:!0,composed:!0,detail:{message:i,step:"form"}})),this.setError(i)}}getNextDates(){const e=[],t=new Date;t.setHours(0,0,0,0);for(let r=0;r<14;r++){const o=new Date(t);o.setDate(t.getDate()+r);const 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=h[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=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((o,s)=>{const a=document.createElement("div");a.className=`step-dot${s===t?" active":s<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 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 n=document.createElement("p");n.className="slot-card-title",n.textContent=s.title;const c=document.createElement("p");c.className="slot-card-meta";const i=this.formatDayList(s.daysOfWeek);if(c.textContent=`${s.durationMin} min · ${i} · ${s.startTime}–${s.endTime}`,a.appendChild(n),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.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",y.forEach(i=>{const l=document.createElement("div");l.className="date-header",l.textContent=i,s.appendChild(l)});const a=((c=o[0])==null?void 0:c.dayOfWeek)??0;for(let i=0;i<a;i++){const l=document.createElement("div");l.className="date-cell empty",s.appendChild(l)}o.forEach(({date:i,label:l,dayOfWeek:g})=>{const p=document.createElement("button"),b=this.isDateAllowed(g);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,s.appendChild(p)}),e.appendChild(s);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 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 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),s.appendChild(n)}),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 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 o=document.createElement("strong");o.textContent=((b=this.selectedSlot)==null?void 0:b.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 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 g=document.createElement("button");g.type="submit",g.className="btn-primary",g.textContent="Confirm Booking",l.appendChild(g),a.appendChild(l),e.appendChild(a);const p=document.createElement("div");return p.appendChild(e),p}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 n=document.createElement("input");return n.className="form-input",n.type=r,n.id=`lsv-${e}`,n.name=e,n.required=o,n.autocomplete=e==="guestName"?"name":e==="guestEmail"?"email":e==="guestPhone"?"tel":"off",s.appendChild(a),s.appendChild(n),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(){this.root.addEventListener("click",t=>{const o=t.target.closest("[data-action]");o&&this.handleAction(o.dataset.action??"",o)});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(),n=(r.get("notes")??"").trim();!o||!s||this.submitBooking(o,s,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,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),m.LsvBooking=f,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
|
|
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"})}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifestreamdynamics/booking-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Embeddable booking widget Web Component for Lifestream Vault",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/lsv-booking.umd.cjs",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"prepublishOnly": "npm run build && npm test"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"jsdom": "^28.
|
|
47
|
+
"jsdom": "^28.0.0",
|
|
48
48
|
"typescript": "^5.7.3",
|
|
49
49
|
"vite": "^6.1.0",
|
|
50
50
|
"vite-plugin-dts": "^4.5.0",
|