@sonordev/site-kit 1.3.0 → 1.3.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.
Files changed (63) hide show
  1. package/dist/{chunk-SQSBAPWA.mjs → chunk-3QKTOTWS.mjs} +5 -5
  2. package/dist/chunk-3QKTOTWS.mjs.map +1 -0
  3. package/dist/{chunk-BFJDUTXK.js → chunk-5NXUENL3.js} +3 -3
  4. package/dist/{chunk-BFJDUTXK.js.map → chunk-5NXUENL3.js.map} +1 -1
  5. package/dist/{chunk-6TFGPXXK.mjs → chunk-5TTTX6NN.mjs} +2 -2
  6. package/dist/{chunk-6TFGPXXK.mjs.map → chunk-5TTTX6NN.mjs.map} +1 -1
  7. package/dist/{chunk-44GSXYHJ.mjs → chunk-J4K335PQ.mjs} +3 -3
  8. package/dist/{chunk-44GSXYHJ.mjs.map → chunk-J4K335PQ.mjs.map} +1 -1
  9. package/dist/chunk-JB6I5PYV.js +939 -0
  10. package/dist/chunk-JB6I5PYV.js.map +1 -0
  11. package/dist/{chunk-7ROZJDXE.js → chunk-JKPJ2QCC.js} +2 -2
  12. package/dist/{chunk-7ROZJDXE.js.map → chunk-JKPJ2QCC.js.map} +1 -1
  13. package/dist/{chunk-JMNSED4O.mjs → chunk-KMLCQYEV.mjs} +3 -3
  14. package/dist/{chunk-JMNSED4O.mjs.map → chunk-KMLCQYEV.mjs.map} +1 -1
  15. package/dist/{chunk-YIL674PV.js → chunk-LTTTIJPN.js} +5 -5
  16. package/dist/{chunk-YIL674PV.js.map → chunk-LTTTIJPN.js.map} +1 -1
  17. package/dist/{chunk-742WMKQC.js → chunk-NZHVGBAF.js} +2 -2
  18. package/dist/{chunk-742WMKQC.js.map → chunk-NZHVGBAF.js.map} +1 -1
  19. package/dist/{chunk-RMOL4TZ6.js → chunk-PJKHCYI5.js} +5 -5
  20. package/dist/chunk-PJKHCYI5.js.map +1 -0
  21. package/dist/{chunk-47Y3YSES.mjs → chunk-QBFTBORL.mjs} +2 -2
  22. package/dist/{chunk-47Y3YSES.mjs.map → chunk-QBFTBORL.mjs.map} +1 -1
  23. package/dist/{chunk-2RHO4KSK.mjs → chunk-RJTBSSZB.mjs} +4 -4
  24. package/dist/{chunk-2RHO4KSK.mjs.map → chunk-RJTBSSZB.mjs.map} +1 -1
  25. package/dist/{chunk-CFEOOJUT.js → chunk-RRBBMEDL.js} +7 -7
  26. package/dist/{chunk-CFEOOJUT.js.map → chunk-RRBBMEDL.js.map} +1 -1
  27. package/dist/chunk-W2TZYAUL.mjs +925 -0
  28. package/dist/chunk-W2TZYAUL.mjs.map +1 -0
  29. package/dist/cli/index.js +1 -1
  30. package/dist/cli/index.mjs +1 -1
  31. package/dist/index.js +60 -938
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +3 -917
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/layout/client.js +2 -2
  36. package/dist/layout/client.mjs +1 -1
  37. package/dist/layout/index.js +6 -6
  38. package/dist/layout/index.mjs +3 -3
  39. package/dist/llms/index.js +12 -12
  40. package/dist/llms/index.mjs +2 -2
  41. package/dist/seo/index.js +41 -53
  42. package/dist/seo/index.js.map +1 -1
  43. package/dist/seo/index.mjs +6 -18
  44. package/dist/seo/index.mjs.map +1 -1
  45. package/dist/seo/server.js +4 -4
  46. package/dist/seo/server.mjs +2 -2
  47. package/dist/{server-api-HTSLBT6F.mjs → server-api-FCTEQOTK.mjs} +3 -3
  48. package/dist/{server-api-HTSLBT6F.mjs.map → server-api-FCTEQOTK.mjs.map} +1 -1
  49. package/dist/{server-api-C5JXIROA.js → server-api-SYINXTN7.js} +21 -21
  50. package/dist/{server-api-C5JXIROA.js.map → server-api-SYINXTN7.js.map} +1 -1
  51. package/dist/setup/index.js +2 -2
  52. package/dist/setup/index.mjs +1 -1
  53. package/dist/setup/server.js +2 -2
  54. package/dist/setup/server.mjs +1 -1
  55. package/dist/sitemap/index.js +2 -2
  56. package/dist/sitemap/index.mjs +1 -1
  57. package/dist/sync/index.js +63 -0
  58. package/dist/sync/index.js.map +1 -0
  59. package/dist/sync/index.mjs +4 -0
  60. package/dist/sync/index.mjs.map +1 -0
  61. package/package.json +6 -1
  62. package/dist/chunk-RMOL4TZ6.js.map +0 -1
  63. package/dist/chunk-SQSBAPWA.mjs.map +0 -1
@@ -0,0 +1,939 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/sync/BookingWidget.tsx
8
+
9
+ // src/sync/api.ts
10
+ var DEFAULT_API_URL = "https://api.sonor.io";
11
+ function getApiConfig() {
12
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || DEFAULT_API_URL : DEFAULT_API_URL;
13
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
14
+ return { apiUrl, apiKey };
15
+ }
16
+ function buildHeaders(apiKey, isPost = false) {
17
+ const headers = {};
18
+ if (isPost) headers["Content-Type"] = "application/json";
19
+ if (apiKey) headers["x-api-key"] = apiKey;
20
+ return headers;
21
+ }
22
+ async function fetchBookingTypes(orgSlug, apiUrl, apiKey) {
23
+ const cfg = getApiConfig();
24
+ const url = apiUrl || cfg.apiUrl;
25
+ const key = apiKey || cfg.apiKey;
26
+ const endpoint = key ? `${url}/sync/widget/types` : `${url}/sync/public/${orgSlug}/types`;
27
+ const response = await fetch(endpoint, { headers: buildHeaders(key) });
28
+ if (!response.ok) {
29
+ const msg = await response.json().catch(() => ({ message: response.statusText }));
30
+ const detail = msg && typeof msg.message === "string" ? msg.message : response.statusText;
31
+ throw new Error(`Failed to fetch booking types: ${response.status} ${detail}`);
32
+ }
33
+ const data = await response.json();
34
+ return data.types || [];
35
+ }
36
+ async function fetchBookingTypeDetails(typeSlug, orgSlug, apiUrl, apiKey) {
37
+ const cfg = getApiConfig();
38
+ const url = apiUrl || cfg.apiUrl;
39
+ const key = apiKey || cfg.apiKey;
40
+ const endpoint = key ? `${url}/sync/widget/types/${typeSlug}` : `${url}/sync/public/${orgSlug}/types/${typeSlug}`;
41
+ const response = await fetch(endpoint, { headers: buildHeaders(key) });
42
+ if (!response.ok) {
43
+ const msg = await response.json().catch(() => ({ message: response.statusText }));
44
+ const detail = msg && typeof msg.message === "string" ? msg.message : response.statusText;
45
+ throw new Error(`Failed to fetch booking type: ${response.status} ${detail}`);
46
+ }
47
+ return response.json();
48
+ }
49
+ async function fetchAvailability(typeSlug, date, orgSlug, apiUrl, apiKey, timezone, hostId) {
50
+ const cfg = getApiConfig();
51
+ const url = apiUrl || cfg.apiUrl;
52
+ const key = apiKey || cfg.apiKey;
53
+ const params = new URLSearchParams({ date });
54
+ if (timezone) params.append("timezone", timezone);
55
+ if (hostId) params.append("hostId", hostId);
56
+ const endpoint = key ? `${url}/sync/widget/availability/${typeSlug}?${params}` : `${url}/sync/public/${orgSlug}/availability/${typeSlug}?${params}`;
57
+ const response = await fetch(endpoint, { headers: buildHeaders(key) });
58
+ if (!response.ok) {
59
+ const msg = await response.json().catch(() => ({ message: response.statusText }));
60
+ let detail = msg && typeof msg.message === "string" ? String(msg.message).trim() : (response.statusText || "").trim();
61
+ if (detail === String(response.status) || /^\d+$/.test(detail)) detail = "";
62
+ const rest = detail ? `${response.status} ${detail}` : String(response.status);
63
+ const fallback = response.status === 404 && !detail ? "No times available. In the Portal, check Sync \u2192 Booking Types (Schedule a Tour) has a host assigned and the project API key is set for this site." : null;
64
+ throw new Error(fallback || `Failed to fetch availability: ${rest}`.trim());
65
+ }
66
+ const data = await response.json();
67
+ const raw = data.slots || [];
68
+ return raw.map((slot) => ({
69
+ start: slot.startTime ?? slot.start,
70
+ end: slot.endTime ?? slot.end,
71
+ hostId: slot.hostId,
72
+ available: slot.available !== false
73
+ }));
74
+ }
75
+ async function createSlotHold(holdData, apiUrl, apiKey) {
76
+ const cfg = getApiConfig();
77
+ const url = apiUrl || cfg.apiUrl;
78
+ const key = apiKey || cfg.apiKey;
79
+ const endpoint = key ? `${url}/sync/widget/hold` : `${url}/sync/public/hold`;
80
+ const response = await fetch(endpoint, {
81
+ method: "POST",
82
+ headers: buildHeaders(key, true),
83
+ body: JSON.stringify(holdData)
84
+ });
85
+ if (!response.ok) {
86
+ throw new Error(`Failed to hold slot: ${response.statusText}`);
87
+ }
88
+ return response.json();
89
+ }
90
+ async function releaseSlotHold(holdId, apiUrl, apiKey) {
91
+ const cfg = getApiConfig();
92
+ const url = apiUrl || cfg.apiUrl;
93
+ const key = apiKey || cfg.apiKey;
94
+ const endpoint = key ? `${url}/sync/widget/hold/${holdId}` : `${url}/sync/public/hold/${holdId}`;
95
+ await fetch(endpoint, {
96
+ method: "DELETE",
97
+ headers: buildHeaders(key)
98
+ });
99
+ }
100
+ async function createBooking(bookingData, apiUrl, apiKey) {
101
+ const cfg = getApiConfig();
102
+ const url = apiUrl || cfg.apiUrl;
103
+ const key = apiKey || cfg.apiKey;
104
+ const endpoint = key ? `${url}/sync/widget/booking` : `${url}/sync/public/booking`;
105
+ const response = await fetch(endpoint, {
106
+ method: "POST",
107
+ headers: buildHeaders(key, true),
108
+ body: JSON.stringify(bookingData)
109
+ });
110
+ if (!response.ok) {
111
+ const errorData = await response.json().catch(() => ({}));
112
+ throw new Error(errorData.message || `Failed to create booking: ${response.statusText}`);
113
+ }
114
+ return response.json();
115
+ }
116
+ async function fetchAvailableDates(typeSlug, startDate, endDate, orgSlug, apiUrl, apiKey, timezone) {
117
+ const availableDates = [];
118
+ const start = new Date(startDate);
119
+ const end = new Date(endDate);
120
+ const current = new Date(start);
121
+ while (current <= end) {
122
+ const dateStr = current.toISOString().split("T")[0];
123
+ try {
124
+ const slots = await fetchAvailability(typeSlug, dateStr, orgSlug, apiUrl, apiKey, timezone);
125
+ if (slots.some((s) => s.available)) {
126
+ availableDates.push(dateStr);
127
+ }
128
+ } catch {
129
+ }
130
+ current.setDate(current.getDate() + 1);
131
+ }
132
+ return availableDates;
133
+ }
134
+ function detectTimezone() {
135
+ try {
136
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
137
+ } catch {
138
+ return "America/New_York";
139
+ }
140
+ }
141
+ function formatTime(isoString, timezone) {
142
+ const date = new Date(isoString);
143
+ return date.toLocaleTimeString("en-US", {
144
+ hour: "numeric",
145
+ minute: "2-digit",
146
+ hour12: true,
147
+ timeZone: timezone
148
+ });
149
+ }
150
+ function formatDate(isoString, timezone) {
151
+ const date = new Date(isoString);
152
+ return date.toLocaleDateString("en-US", {
153
+ weekday: "long",
154
+ month: "long",
155
+ day: "numeric",
156
+ year: "numeric",
157
+ timeZone: timezone
158
+ });
159
+ }
160
+ function formatDuration(minutes) {
161
+ if (minutes < 60) {
162
+ return `${minutes} min`;
163
+ }
164
+ const hours = Math.floor(minutes / 60);
165
+ const mins = minutes % 60;
166
+ if (mins === 0) {
167
+ return `${hours} hour${hours > 1 ? "s" : ""}`;
168
+ }
169
+ return `${hours}h ${mins}m`;
170
+ }
171
+ var DEFAULT_API_URL2 = "https://api.sonor.io";
172
+ var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
173
+ var MONTHS = [
174
+ "January",
175
+ "February",
176
+ "March",
177
+ "April",
178
+ "May",
179
+ "June",
180
+ "July",
181
+ "August",
182
+ "September",
183
+ "October",
184
+ "November",
185
+ "December"
186
+ ];
187
+ function isSameDay(a, b) {
188
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
189
+ }
190
+ function isBeforeDay(a, b) {
191
+ const ac = new Date(a.getFullYear(), a.getMonth(), a.getDate());
192
+ const bc = new Date(b.getFullYear(), b.getMonth(), b.getDate());
193
+ return ac < bc;
194
+ }
195
+ function calendarDays(year, month) {
196
+ const first = new Date(year, month, 1);
197
+ const last = new Date(year, month + 1, 0);
198
+ const cells = [];
199
+ for (let i = 0; i < first.getDay(); i++) cells.push(null);
200
+ for (let d = 1; d <= last.getDate(); d++) cells.push(new Date(year, month, d));
201
+ return cells;
202
+ }
203
+ function ChevronLeft() {
204
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12.5 15L7.5 10L12.5 5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
205
+ }
206
+ function ChevronRight() {
207
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7.5 5L12.5 10L7.5 15", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
208
+ }
209
+ function ClockIcon() {
210
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
211
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "1.2" }),
212
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 4.5V8L10 10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
213
+ ] });
214
+ }
215
+ function GlobeIcon() {
216
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
217
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "1.2" }),
218
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1.5 8H14.5", stroke: "currentColor", strokeWidth: "1.2" }),
219
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 1.5C9.66 3.34 10.61 5.62 10.61 8C10.61 10.38 9.66 12.66 8 14.5C6.34 12.66 5.39 10.38 5.39 8C5.39 5.62 6.34 3.34 8 1.5Z", stroke: "currentColor", strokeWidth: "1.2" })
220
+ ] });
221
+ }
222
+ function CheckCircleIcon() {
223
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "56", height: "56", viewBox: "0 0 56 56", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
224
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "28", cy: "28", r: "28", fill: "var(--bw-primary)" }),
225
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 28.5L24.5 35L38 21.5", stroke: "white", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round" })
226
+ ] });
227
+ }
228
+ function CalendarPlusIcon() {
229
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
230
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1.5", y: "2.5", width: "13", height: "12", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
231
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1.5 6.5H14.5", stroke: "currentColor", strokeWidth: "1.2" }),
232
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 1V4M11 1V4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
233
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 9V12M6.5 10.5H9.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
234
+ ] });
235
+ }
236
+ function ArrowLeftIcon() {
237
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 3L5 8L10 13", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
238
+ }
239
+ function SpinnerIcon() {
240
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: "bw-spinner", children: [
241
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "10", cy: "10", r: "8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeDasharray: "50.265", strokeDashoffset: "25", opacity: "0.3" }),
242
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "10", cy: "10", r: "8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeDasharray: "50.265", strokeDashoffset: "37.7" })
243
+ ] });
244
+ }
245
+ function BookingWidget({
246
+ orgSlug,
247
+ apiKey: propApiKey,
248
+ apiUrl: propApiUrl,
249
+ bookingTypeSlug,
250
+ timezone: propTimezone,
251
+ className = "",
252
+ daysToShow = 60,
253
+ onBookingComplete,
254
+ onError,
255
+ hideTypeSelector = false,
256
+ styles = {}
257
+ }) {
258
+ const apiKey = propApiKey || (typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0);
259
+ const apiUrl = propApiUrl || (typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ : void 0) || DEFAULT_API_URL2;
260
+ const [step, setStep] = react.useState(bookingTypeSlug ? "datetime" : "type");
261
+ const [loading, setLoading] = react.useState(false);
262
+ const [slotsLoading, setSlotsLoading] = react.useState(false);
263
+ const [error, setError] = react.useState(null);
264
+ const [bookingTypes, setBookingTypes] = react.useState([]);
265
+ const [selectedType, setSelectedType] = react.useState(null);
266
+ const [viewMonth, setViewMonth] = react.useState(() => /* @__PURE__ */ new Date());
267
+ const [selectedDate, setSelectedDate] = react.useState(null);
268
+ const [slots, setSlots] = react.useState([]);
269
+ const [selectedSlot, setSelectedSlot] = react.useState(null);
270
+ const [confirmedSlot, setConfirmedSlot] = react.useState(false);
271
+ const [hold, setHold] = react.useState(null);
272
+ const [guestInfo, setGuestInfo] = react.useState({ name: "", email: "" });
273
+ const [bookingResult, setBookingResult] = react.useState(null);
274
+ const [submitting, setSubmitting] = react.useState(false);
275
+ const slotsRef = react.useRef(null);
276
+ const today = react.useMemo(() => {
277
+ const d = /* @__PURE__ */ new Date();
278
+ d.setHours(0, 0, 0, 0);
279
+ return d;
280
+ }, []);
281
+ const timezone = react.useMemo(() => propTimezone || detectTimezone(), [propTimezone]);
282
+ const shortTz = react.useMemo(() => {
283
+ try {
284
+ const parts = Intl.DateTimeFormat("en-US", { timeZone: timezone, timeZoneName: "short" }).formatToParts(/* @__PURE__ */ new Date());
285
+ return parts.find((p) => p.type === "timeZoneName")?.value || timezone;
286
+ } catch {
287
+ return timezone;
288
+ }
289
+ }, [timezone]);
290
+ const days = react.useMemo(() => calendarDays(viewMonth.getFullYear(), viewMonth.getMonth()), [viewMonth]);
291
+ const maxDate = react.useMemo(() => {
292
+ const d = new Date(today);
293
+ d.setDate(d.getDate() + daysToShow);
294
+ return d;
295
+ }, [today, daysToShow]);
296
+ react.useEffect(() => {
297
+ if (bookingTypeSlug) {
298
+ setLoading(true);
299
+ fetchBookingTypeDetails(bookingTypeSlug, orgSlug, apiUrl, apiKey).then((type) => {
300
+ setSelectedType(type);
301
+ setStep("datetime");
302
+ }).catch((err) => {
303
+ setError(err.message);
304
+ onError?.(err);
305
+ }).finally(() => setLoading(false));
306
+ } else {
307
+ setLoading(true);
308
+ fetchBookingTypes(orgSlug, apiUrl, apiKey).then((types) => setBookingTypes(types.filter((t) => t.is_active))).catch((err) => {
309
+ setError(err.message);
310
+ onError?.(err);
311
+ }).finally(() => setLoading(false));
312
+ }
313
+ }, [orgSlug, apiKey, bookingTypeSlug, apiUrl, onError]);
314
+ react.useEffect(() => {
315
+ if (!selectedDate || !selectedType) return;
316
+ const dateStr = selectedDate.toISOString().split("T")[0];
317
+ setSlotsLoading(true);
318
+ setSlots([]);
319
+ setSelectedSlot(null);
320
+ setConfirmedSlot(false);
321
+ fetchAvailability(selectedType.slug, dateStr, orgSlug, apiUrl, apiKey, timezone).then((s) => setSlots(s.filter((slot) => slot.available))).catch((err) => {
322
+ setError(err.message);
323
+ onError?.(err);
324
+ }).finally(() => setSlotsLoading(false));
325
+ }, [selectedDate, selectedType, orgSlug, apiKey, apiUrl, timezone, onError]);
326
+ const handleSlotSelect = react.useCallback((slot) => {
327
+ setSelectedSlot(slot);
328
+ setConfirmedSlot(false);
329
+ }, []);
330
+ const handleSlotConfirm = react.useCallback(async () => {
331
+ if (!selectedType || !selectedSlot) return;
332
+ if (hold) await releaseSlotHold(hold.holdId, apiUrl, apiKey).catch(() => {
333
+ });
334
+ setLoading(true);
335
+ try {
336
+ const newHold = await createSlotHold(
337
+ {
338
+ bookingType: selectedType.slug,
339
+ slotStart: selectedSlot.start,
340
+ slotEnd: selectedSlot.end,
341
+ hostId: selectedSlot.hostId || "",
342
+ sessionId: `bw-${Date.now()}`
343
+ },
344
+ apiUrl,
345
+ apiKey
346
+ );
347
+ setHold(newHold);
348
+ setConfirmedSlot(true);
349
+ setStep("form");
350
+ } catch (err) {
351
+ setError(err.message);
352
+ onError?.(err);
353
+ } finally {
354
+ setLoading(false);
355
+ }
356
+ }, [selectedType, selectedSlot, hold, timezone, apiUrl, apiKey, onError]);
357
+ const handleBookingSubmit = react.useCallback(async (e) => {
358
+ e.preventDefault();
359
+ if (!selectedType || !selectedSlot) return;
360
+ setSubmitting(true);
361
+ setError(null);
362
+ try {
363
+ const result = await createBooking(
364
+ {
365
+ bookingType: selectedType.slug,
366
+ scheduledAt: selectedSlot.start,
367
+ hostId: selectedSlot.hostId || "",
368
+ name: guestInfo.name,
369
+ email: guestInfo.email,
370
+ phone: guestInfo.phone,
371
+ message: guestInfo.notes,
372
+ source: "main-site",
373
+ timezone,
374
+ holdId: hold?.holdId,
375
+ sessionId: `bw-${Date.now()}`,
376
+ sourceUrl: typeof window !== "undefined" ? window.location.href : void 0
377
+ },
378
+ apiUrl,
379
+ apiKey
380
+ );
381
+ setBookingResult(result);
382
+ setStep("success");
383
+ onBookingComplete?.(result);
384
+ } catch (err) {
385
+ setError(err.message);
386
+ onError?.(err);
387
+ } finally {
388
+ setSubmitting(false);
389
+ }
390
+ }, [selectedType, selectedSlot, guestInfo, timezone, hold, apiUrl, apiKey, onBookingComplete, onError]);
391
+ react.useEffect(() => {
392
+ return () => {
393
+ if (hold) releaseSlotHold(hold.holdId, apiUrl, apiKey).catch(() => {
394
+ });
395
+ };
396
+ }, [hold, apiUrl, apiKey]);
397
+ const canGoPrev = viewMonth.getFullYear() > today.getFullYear() || viewMonth.getMonth() > today.getMonth();
398
+ const canGoNext = viewMonth < maxDate;
399
+ const goMonth = (dir) => {
400
+ setViewMonth((prev) => new Date(prev.getFullYear(), prev.getMonth() + dir, 1));
401
+ };
402
+ const groupedSlots = react.useMemo(() => {
403
+ const morning = [];
404
+ const afternoon = [];
405
+ const evening = [];
406
+ for (const s of slots) {
407
+ const h = new Date(s.start).getHours();
408
+ if (h < 12) morning.push(s);
409
+ else if (h < 17) afternoon.push(s);
410
+ else evening.push(s);
411
+ }
412
+ return { morning, afternoon, evening };
413
+ }, [slots]);
414
+ const cssVars = {
415
+ "--bw-primary": styles.primaryColor || "#0069ff",
416
+ "--bw-primary-light": styles.primaryColor ? `color-mix(in srgb, ${styles.primaryColor} 12%, white)` : "#e8f1ff",
417
+ "--bw-primary-hover": styles.primaryColor ? `color-mix(in srgb, ${styles.primaryColor} 90%, black)` : "#0055d4",
418
+ "--bw-radius": styles.borderRadius || "8px",
419
+ "--bw-font": styles.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
420
+ };
421
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `bw-root ${className}`, style: cssVars, children: [
422
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-error", role: "alert", children: [
423
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: error }),
424
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setError(null), "aria-label": "Dismiss error", children: "\xD7" })
425
+ ] }),
426
+ step === "type" && !hideTypeSelector && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-step bw-fade-in", children: [
427
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "bw-heading", children: "Select a Service" }),
428
+ loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-loading", children: [
429
+ /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, {}),
430
+ " Loading services..."
431
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bw-type-list", children: bookingTypes.map((type) => /* @__PURE__ */ jsxRuntime.jsxs(
432
+ "button",
433
+ {
434
+ className: "bw-type-card",
435
+ onClick: () => {
436
+ setSelectedType(type);
437
+ setStep("datetime");
438
+ },
439
+ children: [
440
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-type-name", children: type.name }),
441
+ type.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-type-desc", children: type.description }),
442
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bw-type-meta", children: [
443
+ /* @__PURE__ */ jsxRuntime.jsx(ClockIcon, {}),
444
+ " ",
445
+ formatDuration(type.duration_minutes),
446
+ type.price_cents ? ` \xB7 $${(type.price_cents / 100).toFixed(2)}` : " \xB7 Free"
447
+ ] })
448
+ ]
449
+ },
450
+ type.id
451
+ )) })
452
+ ] }),
453
+ step === "datetime" && selectedType && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-step bw-fade-in", children: [
454
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-info-header", children: [
455
+ !bookingTypeSlug && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "bw-back", onClick: () => {
456
+ setSelectedType(null);
457
+ setStep("type");
458
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(ArrowLeftIcon, {}) }),
459
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
460
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "bw-heading", children: selectedType.name }),
461
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-meta-row", children: [
462
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bw-badge", children: [
463
+ /* @__PURE__ */ jsxRuntime.jsx(ClockIcon, {}),
464
+ " ",
465
+ formatDuration(selectedType.duration_minutes)
466
+ ] }),
467
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bw-badge", children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx(GlobeIcon, {}),
469
+ " ",
470
+ shortTz
471
+ ] })
472
+ ] })
473
+ ] })
474
+ ] }),
475
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-datetime-layout", children: [
476
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-calendar", children: [
477
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-cal-header", children: [
478
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "bw-cal-nav", onClick: () => goMonth(-1), disabled: !canGoPrev, "aria-label": "Previous month", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {}) }),
479
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bw-cal-title", children: [
480
+ MONTHS[viewMonth.getMonth()],
481
+ " ",
482
+ viewMonth.getFullYear()
483
+ ] }),
484
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "bw-cal-nav", onClick: () => goMonth(1), disabled: !canGoNext, "aria-label": "Next month", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, {}) })
485
+ ] }),
486
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bw-cal-weekdays", children: DAYS.map((d) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-cal-wd", children: d }, d)) }),
487
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bw-cal-grid", children: days.map((date, i) => {
488
+ if (!date) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-cal-empty" }, `e-${i}`);
489
+ const past = isBeforeDay(date, today);
490
+ const future = date > maxDate;
491
+ const disabled = past || future;
492
+ const sel = selectedDate && isSameDay(date, selectedDate);
493
+ const isToday = isSameDay(date, today);
494
+ return /* @__PURE__ */ jsxRuntime.jsx(
495
+ "button",
496
+ {
497
+ className: `bw-cal-day${sel ? " selected" : ""}${isToday ? " today" : ""}${disabled ? " disabled" : ""}`,
498
+ onClick: () => !disabled && setSelectedDate(date),
499
+ disabled,
500
+ "aria-label": date.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" }),
501
+ "aria-pressed": sel || void 0,
502
+ children: date.getDate()
503
+ },
504
+ date.toISOString()
505
+ );
506
+ }) })
507
+ ] }),
508
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `bw-times${selectedDate ? " visible" : ""}`, ref: slotsRef, children: !selectedDate ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-times-placeholder", children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx(CalendarPlusIcon, {}),
510
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Select a date to view available times" })
511
+ ] }) : slotsLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-loading", children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, {}),
513
+ " Loading times..."
514
+ ] }) : slots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bw-times-empty", children: "No available times on this date. Try another day." }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-times-scroll", children: [
515
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "bw-times-date", children: selectedDate.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" }) }),
516
+ groupedSlots.morning.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-time-group", children: [
517
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-time-label", children: "Morning" }),
518
+ groupedSlots.morning.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(TimeButton, { slot, selected: selectedSlot?.start === slot.start, confirmed: confirmedSlot && selectedSlot?.start === slot.start, onClick: () => handleSlotSelect(slot), onConfirm: handleSlotConfirm, timezone, loading }, slot.start))
519
+ ] }),
520
+ groupedSlots.afternoon.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-time-group", children: [
521
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-time-label", children: "Afternoon" }),
522
+ groupedSlots.afternoon.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(TimeButton, { slot, selected: selectedSlot?.start === slot.start, confirmed: confirmedSlot && selectedSlot?.start === slot.start, onClick: () => handleSlotSelect(slot), onConfirm: handleSlotConfirm, timezone, loading }, slot.start))
523
+ ] }),
524
+ groupedSlots.evening.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-time-group", children: [
525
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-time-label", children: "Evening" }),
526
+ groupedSlots.evening.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(TimeButton, { slot, selected: selectedSlot?.start === slot.start, confirmed: confirmedSlot && selectedSlot?.start === slot.start, onClick: () => handleSlotSelect(slot), onConfirm: handleSlotConfirm, timezone, loading }, slot.start))
527
+ ] }),
528
+ (groupedSlots.afternoon.length > 0 || groupedSlots.evening.length > 0) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "bw-times-scroll-hint", children: "Scroll for more times" })
529
+ ] }) })
530
+ ] })
531
+ ] }),
532
+ step === "form" && selectedType && selectedSlot && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-step bw-fade-in", children: [
533
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-info-header", children: [
534
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "bw-back", onClick: () => setStep("datetime"), children: /* @__PURE__ */ jsxRuntime.jsx(ArrowLeftIcon, {}) }),
535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
536
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "bw-heading", children: "Your Details" }),
537
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-meta-row", children: [
538
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bw-badge", children: [
539
+ /* @__PURE__ */ jsxRuntime.jsx(ClockIcon, {}),
540
+ " ",
541
+ formatDuration(selectedType.duration_minutes)
542
+ ] }),
543
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bw-badge-accent", children: [
544
+ formatDate(selectedSlot.start, timezone),
545
+ " \xB7 ",
546
+ formatTime(selectedSlot.start, timezone)
547
+ ] })
548
+ ] })
549
+ ] })
550
+ ] }),
551
+ hold && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-hold-notice", children: [
552
+ /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, {}),
553
+ " Slot held for you \u2014 complete your details to confirm."
554
+ ] }),
555
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleBookingSubmit, className: "bw-form", children: [
556
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-field", children: [
557
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: "bw-name", children: [
558
+ "Name ",
559
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-req", children: "*" })
560
+ ] }),
561
+ /* @__PURE__ */ jsxRuntime.jsx(
562
+ "input",
563
+ {
564
+ id: "bw-name",
565
+ type: "text",
566
+ required: true,
567
+ value: guestInfo.name,
568
+ onChange: (e) => setGuestInfo((p) => ({ ...p, name: e.target.value })),
569
+ placeholder: "Jane Smith",
570
+ autoComplete: "name"
571
+ }
572
+ )
573
+ ] }),
574
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-field", children: [
575
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: "bw-email", children: [
576
+ "Email ",
577
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-req", children: "*" })
578
+ ] }),
579
+ /* @__PURE__ */ jsxRuntime.jsx(
580
+ "input",
581
+ {
582
+ id: "bw-email",
583
+ type: "email",
584
+ required: true,
585
+ value: guestInfo.email,
586
+ onChange: (e) => setGuestInfo((p) => ({ ...p, email: e.target.value })),
587
+ placeholder: "jane@example.com",
588
+ autoComplete: "email"
589
+ }
590
+ )
591
+ ] }),
592
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-field", children: [
593
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bw-phone", children: "Phone" }),
594
+ /* @__PURE__ */ jsxRuntime.jsx(
595
+ "input",
596
+ {
597
+ id: "bw-phone",
598
+ type: "tel",
599
+ value: guestInfo.phone || "",
600
+ onChange: (e) => setGuestInfo((p) => ({ ...p, phone: e.target.value })),
601
+ placeholder: "(555) 123-4567",
602
+ autoComplete: "tel"
603
+ }
604
+ )
605
+ ] }),
606
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-field", children: [
607
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bw-notes", children: "Notes" }),
608
+ /* @__PURE__ */ jsxRuntime.jsx(
609
+ "textarea",
610
+ {
611
+ id: "bw-notes",
612
+ value: guestInfo.notes || "",
613
+ onChange: (e) => setGuestInfo((p) => ({ ...p, notes: e.target.value })),
614
+ placeholder: "Anything you'd like us to know...",
615
+ rows: 3
616
+ }
617
+ )
618
+ ] }),
619
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "bw-submit", disabled: submitting || !guestInfo.name || !guestInfo.email, children: submitting ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
620
+ /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, {}),
621
+ " Confirming..."
622
+ ] }) : "Confirm Booking" })
623
+ ] })
624
+ ] }),
625
+ step === "success" && bookingResult && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-step bw-fade-in bw-success", children: [
626
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bw-success-icon", children: /* @__PURE__ */ jsxRuntime.jsx(CheckCircleIcon, {}) }),
627
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "bw-heading", children: "You\u2019re Booked!" }),
628
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-details-card", children: [
629
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-detail-row", children: [
630
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-detail-label", children: "When" }),
631
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bw-detail-value", children: [
632
+ formatDate(bookingResult.booking.scheduledAt, timezone),
633
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
634
+ formatTime(bookingResult.booking.scheduledAt, timezone),
635
+ " (",
636
+ shortTz,
637
+ ")"
638
+ ] })
639
+ ] }),
640
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-detail-row", children: [
641
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-detail-label", children: "Duration" }),
642
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-detail-value", children: formatDuration(bookingResult.booking.durationMinutes) })
643
+ ] }),
644
+ bookingResult.booking.hostName && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-detail-row", children: [
645
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-detail-label", children: "With" }),
646
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-detail-value", children: bookingResult.booking.hostName })
647
+ ] })
648
+ ] }),
649
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "bw-cal-links-label", children: "Add to your calendar" }),
650
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-cal-links", children: [
651
+ /* @__PURE__ */ jsxRuntime.jsxs("a", { href: bookingResult.calendarLinks.google, target: "_blank", rel: "noopener noreferrer", className: "bw-cal-link", children: [
652
+ /* @__PURE__ */ jsxRuntime.jsx(CalendarPlusIcon, {}),
653
+ " Google"
654
+ ] }),
655
+ /* @__PURE__ */ jsxRuntime.jsxs("a", { href: bookingResult.calendarLinks.outlook, target: "_blank", rel: "noopener noreferrer", className: "bw-cal-link", children: [
656
+ /* @__PURE__ */ jsxRuntime.jsx(CalendarPlusIcon, {}),
657
+ " Outlook"
658
+ ] }),
659
+ /* @__PURE__ */ jsxRuntime.jsxs("a", { href: bookingResult.calendarLinks.ics, download: true, className: "bw-cal-link", children: [
660
+ /* @__PURE__ */ jsxRuntime.jsx(CalendarPlusIcon, {}),
661
+ " iCal"
662
+ ] })
663
+ ] }),
664
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "bw-email-notice", children: [
665
+ "If your host uses Google Calendar, a calendar invite will be sent to ",
666
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: guestInfo.email }),
667
+ "."
668
+ ] })
669
+ ] }),
670
+ loading && step === "datetime" && !selectedType && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-loading", children: [
671
+ /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, {}),
672
+ " Loading..."
673
+ ] }),
674
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: WIDGET_CSS })
675
+ ] });
676
+ }
677
+ function TimeButton({
678
+ slot,
679
+ selected,
680
+ confirmed,
681
+ onClick,
682
+ onConfirm,
683
+ timezone,
684
+ loading
685
+ }) {
686
+ if (selected && !confirmed) {
687
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bw-time-btn-wrap selected", children: [
688
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bw-time-text", children: formatTime(slot.start, timezone) }),
689
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "bw-time-confirm", onClick: onConfirm, disabled: loading, children: loading ? /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, {}) : "Confirm" })
690
+ ] });
691
+ }
692
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { className: `bw-time-btn${selected ? " selected" : ""}`, onClick, disabled: loading, children: formatTime(slot.start, timezone) });
693
+ }
694
+ var WIDGET_CSS = `
695
+ /* \u2500\u2500 Base \u2500\u2500 */
696
+ .bw-root {
697
+ font-family: var(--bw-font);
698
+ color: #1a1a1a;
699
+ line-height: 1.5;
700
+ -webkit-font-smoothing: antialiased;
701
+ box-sizing: border-box;
702
+ }
703
+ .bw-root *, .bw-root *::before, .bw-root *::after { box-sizing: border-box; }
704
+
705
+ /* \u2500\u2500 Animation \u2500\u2500 */
706
+ @keyframes bw-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
707
+ @keyframes bw-spin { to { transform: rotate(360deg); } }
708
+ .bw-fade-in { animation: bw-fade-in 0.25s ease-out; }
709
+ .bw-spinner { animation: bw-spin 0.8s linear infinite; }
710
+
711
+ /* \u2500\u2500 Error \u2500\u2500 */
712
+ .bw-error {
713
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
714
+ background: #fef2f2; border: 1px solid #fecaca; color: #b91c1c;
715
+ padding: 10px 14px; border-radius: var(--bw-radius); margin-bottom: 16px; font-size: 0.875rem;
716
+ }
717
+ .bw-error button { background: none; border: none; color: inherit; cursor: pointer; font-size: 1.25rem; line-height: 1; padding: 0; }
718
+
719
+ /* \u2500\u2500 Headings \u2500\u2500 */
720
+ .bw-heading { font-size: 1.125rem; font-weight: 600; margin: 0; letter-spacing: -0.01em; }
721
+ .bw-meta-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
722
+ .bw-badge {
723
+ display: inline-flex; align-items: center; gap: 4px;
724
+ font-size: 0.8125rem; color: #6b7280; font-weight: 400;
725
+ }
726
+ .bw-badge-accent {
727
+ display: inline-flex; align-items: center; gap: 4px;
728
+ font-size: 0.8125rem; color: var(--bw-primary); font-weight: 500;
729
+ }
730
+
731
+ /* \u2500\u2500 Info Header \u2500\u2500 */
732
+ .bw-info-header { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 20px; }
733
+ .bw-back {
734
+ display: inline-flex; align-items: center; justify-content: center;
735
+ width: 32px; height: 32px; border-radius: 50%; border: 1px solid #e5e7eb;
736
+ background: #fff; cursor: pointer; color: #4b5563; flex-shrink: 0; margin-top: 1px;
737
+ transition: all 0.15s;
738
+ }
739
+ .bw-back:hover { background: #f3f4f6; border-color: #d1d5db; }
740
+
741
+ /* \u2500\u2500 Loading \u2500\u2500 */
742
+ .bw-loading {
743
+ display: flex; align-items: center; justify-content: center; gap: 8px;
744
+ padding: 32px; color: #6b7280; font-size: 0.875rem;
745
+ }
746
+
747
+ /* \u2500\u2500 Type Selection \u2500\u2500 */
748
+ .bw-type-list { display: flex; flex-direction: column; gap: 10px; }
749
+ .bw-type-card {
750
+ display: flex; flex-direction: column; text-align: left; gap: 4px;
751
+ padding: 16px; border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
752
+ background: #fff; cursor: pointer; transition: all 0.15s;
753
+ }
754
+ .bw-type-card:hover { border-color: var(--bw-primary); box-shadow: 0 0 0 3px var(--bw-primary-light); }
755
+ .bw-type-name { font-weight: 600; font-size: 0.9375rem; }
756
+ .bw-type-desc { color: #6b7280; font-size: 0.8125rem; }
757
+ .bw-type-meta { display: flex; align-items: center; gap: 4px; font-size: 0.8125rem; color: #9ca3af; margin-top: 4px; }
758
+
759
+ /* \u2500\u2500 Date-Time Layout \u2500\u2500 */
760
+ .bw-datetime-layout {
761
+ display: flex; gap: 0; border-top: 1px solid #f0f0f0; padding-top: 16px;
762
+ }
763
+ @media (max-width: 559px) {
764
+ .bw-datetime-layout { flex-direction: column; }
765
+ }
766
+ @media (min-width: 560px) {
767
+ .bw-datetime-layout { min-height: 400px; }
768
+ .bw-calendar { flex: 1 1 auto; padding-right: 16px; border-right: 1px solid #f0f0f0; }
769
+ .bw-times { width: 180px; flex-shrink: 0; padding-left: 16px; }
770
+ }
771
+
772
+ /* \u2500\u2500 Calendar \u2500\u2500 */
773
+ .bw-cal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
774
+ .bw-cal-title { font-weight: 600; font-size: 0.9375rem; }
775
+ .bw-cal-nav {
776
+ display: inline-flex; align-items: center; justify-content: center;
777
+ width: 32px; height: 32px; border-radius: 50%; border: none;
778
+ background: transparent; cursor: pointer; color: #374151; transition: background 0.15s;
779
+ }
780
+ .bw-cal-nav:hover:not(:disabled) { background: #f3f4f6; }
781
+ .bw-cal-nav:disabled { opacity: 0.25; cursor: default; }
782
+
783
+ .bw-cal-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 0; text-align: center; margin-bottom: 4px; }
784
+ .bw-cal-wd { font-size: 0.6875rem; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.04em; padding: 4px 0; }
785
+
786
+ .bw-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
787
+ .bw-cal-empty { aspect-ratio: 1; }
788
+ .bw-cal-day {
789
+ aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
790
+ border: none; background: none; border-radius: 50%;
791
+ font-size: 0.8125rem; font-weight: 500; cursor: pointer;
792
+ color: #1a1a1a; transition: all 0.15s; position: relative;
793
+ }
794
+ .bw-cal-day:hover:not(.disabled):not(.selected) { background: #f3f4f6; }
795
+ .bw-cal-day.today:not(.selected)::after {
796
+ content: ''; position: absolute; bottom: 3px; left: 50%; transform: translateX(-50%);
797
+ width: 4px; height: 4px; border-radius: 50%; background: var(--bw-primary);
798
+ }
799
+ .bw-cal-day.selected {
800
+ background: var(--bw-primary); color: #fff; font-weight: 600;
801
+ }
802
+ .bw-cal-day.disabled { color: #d1d5db; cursor: default; }
803
+
804
+ /* \u2500\u2500 Time Slots \u2500\u2500 */
805
+ .bw-times { transition: opacity 0.2s; }
806
+ .bw-times:not(.visible) { opacity: 0.5; }
807
+ .bw-times.visible { opacity: 1; }
808
+ .bw-times-placeholder {
809
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
810
+ height: 100%; min-height: 160px; color: #9ca3af; font-size: 0.8125rem; text-align: center; padding: 16px;
811
+ }
812
+ .bw-times-empty {
813
+ display: flex; align-items: center; justify-content: center;
814
+ height: 100%; min-height: 160px; color: #9ca3af; font-size: 0.8125rem; text-align: center; padding: 16px;
815
+ }
816
+ .bw-times-scroll { overflow-y: auto; max-height: 400px; overflow-x: hidden; }
817
+ .bw-times-date { font-weight: 600; font-size: 0.8125rem; color: #374151; margin: 0 0 12px 0; }
818
+ .bw-time-group { margin-bottom: 16px; }
819
+ .bw-times-scroll-hint {
820
+ font-size: 0.6875rem; color: #9ca3af; text-align: center; margin: 8px 0 0 0; padding-bottom: 8px;
821
+ }
822
+ .bw-time-label { display: block; font-size: 0.6875rem; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 6px; }
823
+
824
+ .bw-time-btn {
825
+ display: block; width: 100%;
826
+ padding: 10px 12px; margin-bottom: 6px;
827
+ border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
828
+ background: #fff; cursor: pointer;
829
+ font-size: 0.875rem; font-weight: 500; color: var(--bw-primary); text-align: center;
830
+ transition: all 0.15s;
831
+ }
832
+ .bw-time-btn:hover:not(:disabled) {
833
+ border-color: var(--bw-primary); background: var(--bw-primary-light);
834
+ }
835
+ .bw-time-btn.selected {
836
+ border-color: var(--bw-primary); background: var(--bw-primary); color: #fff;
837
+ }
838
+ .bw-time-btn:disabled { opacity: 0.5; cursor: not-allowed; }
839
+
840
+ .bw-time-btn-wrap {
841
+ display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
842
+ border: 1.5px solid var(--bw-primary); border-radius: var(--bw-radius); overflow: hidden;
843
+ animation: bw-fade-in 0.15s ease-out;
844
+ }
845
+ .bw-time-btn-wrap .bw-time-text {
846
+ flex: 1; padding: 10px 12px; font-size: 0.875rem; font-weight: 500; color: #374151; text-align: center;
847
+ background: var(--bw-primary-light);
848
+ }
849
+ .bw-time-confirm {
850
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px;
851
+ padding: 10px 16px; border: none;
852
+ background: var(--bw-primary); color: #fff;
853
+ font-size: 0.8125rem; font-weight: 600; cursor: pointer;
854
+ transition: background 0.15s;
855
+ }
856
+ .bw-time-confirm:hover:not(:disabled) { background: var(--bw-primary-hover); }
857
+ .bw-time-confirm:disabled { opacity: 0.7; cursor: not-allowed; }
858
+
859
+ /* \u2500\u2500 Form \u2500\u2500 */
860
+ .bw-hold-notice {
861
+ display: flex; align-items: center; gap: 8px;
862
+ background: var(--bw-primary-light); color: var(--bw-primary);
863
+ padding: 10px 14px; border-radius: var(--bw-radius);
864
+ font-size: 0.8125rem; font-weight: 500; margin-bottom: 20px;
865
+ }
866
+ .bw-form { display: flex; flex-direction: column; gap: 16px; }
867
+ .bw-field label {
868
+ display: block; font-size: 0.8125rem; font-weight: 500; color: #374151; margin-bottom: 4px;
869
+ }
870
+ .bw-req { color: #ef4444; }
871
+ .bw-field input, .bw-field textarea {
872
+ display: block; width: 100%;
873
+ padding: 10px 12px; border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
874
+ font-size: 0.9375rem; font-family: inherit; color: #1a1a1a; background: #fff;
875
+ transition: border-color 0.15s, box-shadow 0.15s;
876
+ }
877
+ .bw-field input::placeholder, .bw-field textarea::placeholder { color: #c0c5cc; }
878
+ .bw-field input:focus, .bw-field textarea:focus {
879
+ outline: none; border-color: var(--bw-primary);
880
+ box-shadow: 0 0 0 3px var(--bw-primary-light);
881
+ }
882
+ .bw-submit {
883
+ display: inline-flex; align-items: center; justify-content: center; gap: 8px;
884
+ width: 100%; padding: 14px; margin-top: 4px;
885
+ background: var(--bw-primary); color: #fff; border: none;
886
+ border-radius: var(--bw-radius); font-size: 0.9375rem; font-weight: 600;
887
+ cursor: pointer; transition: background 0.15s;
888
+ }
889
+ .bw-submit:hover:not(:disabled) { background: var(--bw-primary-hover); }
890
+ .bw-submit:disabled { opacity: 0.55; cursor: not-allowed; }
891
+
892
+ /* \u2500\u2500 Success \u2500\u2500 */
893
+ .bw-success { text-align: center; }
894
+ .bw-success-icon { margin: 0 auto 16px; width: 56px; height: 56px; animation: bw-fade-in 0.4s ease-out; }
895
+ .bw-conf-code {
896
+ display: inline-block; font-family: 'SF Mono', 'Fira Code', monospace;
897
+ background: #f3f4f6; padding: 6px 14px; border-radius: 6px;
898
+ font-size: 0.8125rem; color: #6b7280; margin: 4px 0 20px; letter-spacing: 0.04em;
899
+ }
900
+
901
+ .bw-details-card {
902
+ text-align: left; background: #f9fafb; border: 1px solid #f0f0f0;
903
+ border-radius: var(--bw-radius); padding: 16px; margin-bottom: 20px;
904
+ }
905
+ .bw-detail-row { display: flex; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
906
+ .bw-detail-row:last-child { border-bottom: none; }
907
+ .bw-detail-label { width: 70px; flex-shrink: 0; font-size: 0.8125rem; color: #9ca3af; font-weight: 500; }
908
+ .bw-detail-value { font-size: 0.875rem; font-weight: 500; color: #374151; }
909
+
910
+ .bw-cal-links-label { font-size: 0.8125rem; color: #6b7280; margin: 0 0 8px 0; }
911
+ .bw-cal-links { display: flex; gap: 8px; justify-content: center; margin-bottom: 20px; flex-wrap: wrap; }
912
+ .bw-cal-link {
913
+ display: inline-flex; align-items: center; gap: 6px;
914
+ padding: 8px 14px; border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
915
+ text-decoration: none; color: #374151; font-size: 0.8125rem; font-weight: 500;
916
+ transition: all 0.15s;
917
+ }
918
+ .bw-cal-link:hover { border-color: var(--bw-primary); color: var(--bw-primary); }
919
+
920
+ .bw-email-notice { font-size: 0.8125rem; color: #9ca3af; margin: 0; }
921
+ .bw-email-notice strong { color: #374151; font-weight: 500; }
922
+ `;
923
+ var BookingWidget_default = BookingWidget;
924
+
925
+ exports.BookingWidget = BookingWidget;
926
+ exports.BookingWidget_default = BookingWidget_default;
927
+ exports.createBooking = createBooking;
928
+ exports.createSlotHold = createSlotHold;
929
+ exports.detectTimezone = detectTimezone;
930
+ exports.fetchAvailability = fetchAvailability;
931
+ exports.fetchAvailableDates = fetchAvailableDates;
932
+ exports.fetchBookingTypeDetails = fetchBookingTypeDetails;
933
+ exports.fetchBookingTypes = fetchBookingTypes;
934
+ exports.formatDate = formatDate;
935
+ exports.formatDuration = formatDuration;
936
+ exports.formatTime = formatTime;
937
+ exports.releaseSlotHold = releaseSlotHold;
938
+ //# sourceMappingURL=chunk-JB6I5PYV.js.map
939
+ //# sourceMappingURL=chunk-JB6I5PYV.js.map