@riverbankcms/sdk 0.1.0 → 0.2.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 (176) hide show
  1. package/dist/cli/index.js +4840 -9
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/client/bookings.d.mts +82 -2
  4. package/dist/client/bookings.d.ts +82 -2
  5. package/dist/client/bookings.js +1623 -3
  6. package/dist/client/bookings.js.map +1 -1
  7. package/dist/client/bookings.mjs +1610 -5
  8. package/dist/client/bookings.mjs.map +1 -1
  9. package/dist/client/client.d.mts +8 -5
  10. package/dist/client/client.d.ts +8 -5
  11. package/dist/client/client.js +16856 -322
  12. package/dist/client/client.js.map +1 -1
  13. package/dist/client/client.mjs +16838 -307
  14. package/dist/client/client.mjs.map +1 -1
  15. package/dist/client/hooks.d.mts +10 -7
  16. package/dist/client/hooks.d.ts +10 -7
  17. package/dist/client/hooks.js +5074 -4
  18. package/dist/client/hooks.js.map +1 -1
  19. package/dist/client/hooks.mjs +5074 -4
  20. package/dist/client/hooks.mjs.map +1 -1
  21. package/dist/client/rendering/client.d.mts +7 -1
  22. package/dist/client/rendering/client.d.ts +7 -1
  23. package/dist/client/rendering/client.js +17388 -2
  24. package/dist/client/rendering/client.js.map +1 -1
  25. package/dist/client/rendering/client.mjs +17382 -2
  26. package/dist/client/rendering/client.mjs.map +1 -1
  27. package/dist/client/resolver-BhueZVxZ.d.mts +61 -0
  28. package/dist/client/resolver-BhueZVxZ.d.ts +61 -0
  29. package/dist/client/usePage-BBcFCxOU.d.ts +6297 -0
  30. package/dist/client/usePage-BydHcMYB.d.mts +6297 -0
  31. package/dist/server/Layout-CLg8oH_S.d.ts +44 -0
  32. package/dist/server/Layout-DK_9OOgb.d.mts +44 -0
  33. package/dist/server/chunk-3J46ILMJ.mjs +2111 -0
  34. package/dist/server/chunk-3J46ILMJ.mjs.map +1 -0
  35. package/dist/server/{chunk-JB4LIEFS.js → chunk-5R4NMVXA.js} +15 -8
  36. package/dist/server/chunk-5R4NMVXA.js.map +1 -0
  37. package/dist/server/{chunk-ADREPXFU.js → chunk-62ZJI564.js} +3 -3
  38. package/dist/server/{chunk-ADREPXFU.js.map → chunk-62ZJI564.js.map} +1 -1
  39. package/dist/server/chunk-7DS4Q3GA.mjs +333 -0
  40. package/dist/server/chunk-7DS4Q3GA.mjs.map +1 -0
  41. package/dist/server/chunk-BJTO5JO5.mjs +11 -0
  42. package/dist/server/{chunk-4Z5FBFRL.mjs → chunk-BPKYRPCQ.mjs} +7 -3
  43. package/dist/server/{chunk-4Z5FBFRL.mjs.map → chunk-BPKYRPCQ.mjs.map} +1 -1
  44. package/dist/server/chunk-DGUM43GV.js +11 -0
  45. package/dist/server/chunk-DGUM43GV.js.map +1 -0
  46. package/dist/server/chunk-EGTDJ4PL.js +5461 -0
  47. package/dist/server/chunk-EGTDJ4PL.js.map +1 -0
  48. package/dist/server/chunk-FK64TZBT.mjs +831 -0
  49. package/dist/server/chunk-FK64TZBT.mjs.map +1 -0
  50. package/dist/server/chunk-GKYNDDJS.js +2111 -0
  51. package/dist/server/chunk-GKYNDDJS.js.map +1 -0
  52. package/dist/server/chunk-HOY77YBF.js +333 -0
  53. package/dist/server/chunk-HOY77YBF.js.map +1 -0
  54. package/dist/server/chunk-INWKF3IC.js +831 -0
  55. package/dist/server/chunk-INWKF3IC.js.map +1 -0
  56. package/dist/server/{chunk-2RW5HAQQ.mjs → chunk-JTAERCX2.mjs} +2 -2
  57. package/dist/server/chunk-O5DC7MYW.mjs +9606 -0
  58. package/dist/server/chunk-O5DC7MYW.mjs.map +1 -0
  59. package/dist/server/{chunk-PEAXKTDU.mjs → chunk-OP2GHK27.mjs} +2 -2
  60. package/dist/server/{chunk-WKG57P2H.mjs → chunk-PN3CHDVX.mjs} +10 -3
  61. package/dist/server/{chunk-WKG57P2H.mjs.map → chunk-PN3CHDVX.mjs.map} +1 -1
  62. package/dist/server/chunk-SF63XAX7.js +9606 -0
  63. package/dist/server/chunk-SF63XAX7.js.map +1 -0
  64. package/dist/server/{chunk-F472SMKX.js → chunk-TO7FD6TQ.js} +4 -4
  65. package/dist/server/{chunk-F472SMKX.js.map → chunk-TO7FD6TQ.js.map} +1 -1
  66. package/dist/server/chunk-USQF2XTU.mjs +5461 -0
  67. package/dist/server/chunk-USQF2XTU.mjs.map +1 -0
  68. package/dist/server/{chunk-SW7LE4M3.js → chunk-XLVL5WPH.js} +12 -8
  69. package/dist/server/chunk-XLVL5WPH.js.map +1 -0
  70. package/dist/server/components-BzdA6NAc.d.mts +305 -0
  71. package/dist/server/components-DhIcstww.d.ts +305 -0
  72. package/dist/server/components.d.mts +13 -49
  73. package/dist/server/components.d.ts +13 -49
  74. package/dist/server/components.js +7 -4
  75. package/dist/server/components.js.map +1 -1
  76. package/dist/server/components.mjs +9 -6
  77. package/dist/server/components.mjs.map +1 -1
  78. package/dist/server/config-validation.d.mts +2 -2
  79. package/dist/server/config-validation.d.ts +2 -2
  80. package/dist/server/config-validation.js +6 -3
  81. package/dist/server/config-validation.js.map +1 -1
  82. package/dist/server/config-validation.mjs +5 -2
  83. package/dist/server/config.d.mts +3 -3
  84. package/dist/server/config.d.ts +3 -3
  85. package/dist/server/config.js +6 -3
  86. package/dist/server/config.js.map +1 -1
  87. package/dist/server/config.mjs +5 -2
  88. package/dist/server/config.mjs.map +1 -1
  89. package/dist/server/data.d.mts +9 -8
  90. package/dist/server/data.d.ts +9 -8
  91. package/dist/server/data.js +4 -2
  92. package/dist/server/data.js.map +1 -1
  93. package/dist/server/data.mjs +3 -1
  94. package/dist/server/{index-C6M0Wfjq.d.ts → index-BB28KAui.d.ts} +1 -1
  95. package/dist/server/{index-B0yI_V6Z.d.mts → index-C_FVup_o.d.mts} +1 -1
  96. package/dist/server/index.d.mts +1554 -5
  97. package/dist/server/index.d.ts +1554 -5
  98. package/dist/server/index.js +4 -4
  99. package/dist/server/index.js.map +1 -1
  100. package/dist/server/index.mjs +4 -4
  101. package/dist/server/index.mjs.map +1 -1
  102. package/dist/server/{loadContent-CJcbYF3J.d.ts → loadContent-AQOBf_gP.d.ts} +4 -4
  103. package/dist/server/{loadContent-zhlL4YSE.d.mts → loadContent-DBmprsB4.d.mts} +4 -4
  104. package/dist/server/loadPage-3ECPF426.js +11 -0
  105. package/dist/server/loadPage-3ECPF426.js.map +1 -0
  106. package/dist/server/{loadPage-CCf15nt8.d.mts → loadPage-BMg8PJxJ.d.ts} +146 -5
  107. package/dist/server/loadPage-LW273NYO.mjs +11 -0
  108. package/dist/server/loadPage-LW273NYO.mjs.map +1 -0
  109. package/dist/server/{loadPage-BYmVMk0V.d.ts → loadPage-pg4HimlK.d.mts} +146 -5
  110. package/dist/server/metadata.d.mts +9 -6
  111. package/dist/server/metadata.d.ts +9 -6
  112. package/dist/server/metadata.js +3 -1
  113. package/dist/server/metadata.js.map +1 -1
  114. package/dist/server/metadata.mjs +2 -0
  115. package/dist/server/metadata.mjs.map +1 -1
  116. package/dist/server/rendering/server.d.mts +9 -7
  117. package/dist/server/rendering/server.d.ts +9 -7
  118. package/dist/server/rendering/server.js +7 -4
  119. package/dist/server/rendering/server.js.map +1 -1
  120. package/dist/server/rendering/server.mjs +6 -3
  121. package/dist/server/rendering.d.mts +172 -9
  122. package/dist/server/rendering.d.ts +172 -9
  123. package/dist/server/rendering.js +12 -9
  124. package/dist/server/rendering.js.map +1 -1
  125. package/dist/server/rendering.mjs +14 -11
  126. package/dist/server/rendering.mjs.map +1 -1
  127. package/dist/server/routing.d.mts +9 -6
  128. package/dist/server/routing.d.ts +9 -6
  129. package/dist/server/routing.js +4 -2
  130. package/dist/server/routing.js.map +1 -1
  131. package/dist/server/routing.mjs +3 -1
  132. package/dist/server/routing.mjs.map +1 -1
  133. package/dist/server/schema-Bpy9N5ZI.d.mts +1870 -0
  134. package/dist/server/schema-Bpy9N5ZI.d.ts +1870 -0
  135. package/dist/server/server.d.mts +11 -8
  136. package/dist/server/server.d.ts +11 -8
  137. package/dist/server/server.js +7 -5
  138. package/dist/server/server.js.map +1 -1
  139. package/dist/server/server.mjs +6 -4
  140. package/dist/server/theme-bridge.js +13 -10
  141. package/dist/server/theme-bridge.js.map +1 -1
  142. package/dist/server/theme-bridge.mjs +10 -7
  143. package/dist/server/theme-bridge.mjs.map +1 -1
  144. package/dist/server/theme.js +3 -1
  145. package/dist/server/theme.js.map +1 -1
  146. package/dist/server/theme.mjs +2 -0
  147. package/dist/server/theme.mjs.map +1 -1
  148. package/dist/server/{types-BCeqWtI2.d.ts → types--u4GLCAY.d.ts} +1 -1
  149. package/dist/server/types-BprgZt-t.d.ts +4149 -0
  150. package/dist/server/types-C0G9IxWO.d.mts +4149 -0
  151. package/dist/server/{types-Bbo01M7P.d.mts → types-_nDnPHpv.d.mts} +27 -1
  152. package/dist/server/{types-Bbo01M7P.d.ts → types-_nDnPHpv.d.ts} +27 -1
  153. package/dist/server/{types-BCeqWtI2.d.mts → types-_zWJTgv0.d.mts} +1 -1
  154. package/package.json +15 -15
  155. package/dist/server/chunk-3KKZVGH4.mjs +0 -179
  156. package/dist/server/chunk-3KKZVGH4.mjs.map +0 -1
  157. package/dist/server/chunk-4Z3GPTCS.js +0 -179
  158. package/dist/server/chunk-4Z3GPTCS.js.map +0 -1
  159. package/dist/server/chunk-JB4LIEFS.js.map +0 -1
  160. package/dist/server/chunk-QQ6U4QX6.js +0 -120
  161. package/dist/server/chunk-QQ6U4QX6.js.map +0 -1
  162. package/dist/server/chunk-R5YGLRUG.mjs +0 -122
  163. package/dist/server/chunk-R5YGLRUG.mjs.map +0 -1
  164. package/dist/server/chunk-SW7LE4M3.js.map +0 -1
  165. package/dist/server/chunk-W3K7LVPS.mjs +0 -120
  166. package/dist/server/chunk-W3K7LVPS.mjs.map +0 -1
  167. package/dist/server/chunk-YHEZMVTS.js +0 -122
  168. package/dist/server/chunk-YHEZMVTS.js.map +0 -1
  169. package/dist/server/loadPage-DVH3DW6E.js +0 -9
  170. package/dist/server/loadPage-DVH3DW6E.js.map +0 -1
  171. package/dist/server/loadPage-PHQZ6XQZ.mjs +0 -9
  172. package/dist/server/types-C6gmRHLe.d.mts +0 -150
  173. package/dist/server/types-C6gmRHLe.d.ts +0 -150
  174. /package/dist/server/{loadPage-PHQZ6XQZ.mjs.map → chunk-BJTO5JO5.mjs.map} +0 -0
  175. /package/dist/server/{chunk-2RW5HAQQ.mjs.map → chunk-JTAERCX2.mjs.map} +0 -0
  176. /package/dist/server/{chunk-PEAXKTDU.mjs.map → chunk-OP2GHK27.mjs.map} +0 -0
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  "use strict";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,16 +18,1634 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/bookings/index.ts
22
32
  var bookings_exports = {};
23
33
  __export(bookings_exports, {
24
- BookingFormClient: () => import_client.BookingFormClient,
25
- useBookingFormConfig: () => import_client.useBookingFormConfig
34
+ BookingFormClient: () => BookingFormClient,
35
+ useBookingFormConfig: () => useBookingFormConfig
26
36
  });
27
37
  module.exports = __toCommonJS(bookings_exports);
28
- var import_client = require("@riverbankcms/blocks/client");
38
+
39
+ // ../blocks/src/system/runtime/utils/api-url.ts
40
+ function getCmsApiUrl() {
41
+ const builderApiUrl = process.env.NEXT_PUBLIC_BUILDER_API_URL;
42
+ if (builderApiUrl) {
43
+ return builderApiUrl.replace(/\/$/, "");
44
+ }
45
+ const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;
46
+ if (dashboardUrl) {
47
+ const base = dashboardUrl.replace(/\/$/, "");
48
+ return `${base}/api`;
49
+ }
50
+ const legacyApiUrl = process.env.NEXT_PUBLIC_CMS_API_URL;
51
+ if (legacyApiUrl) {
52
+ return legacyApiUrl.replace(/\/$/, "");
53
+ }
54
+ throw new Error(
55
+ "No API URL configured. Set NEXT_PUBLIC_BUILDER_API_URL (SDK sites) or NEXT_PUBLIC_DASHBOARD_URL (frontend app)."
56
+ );
57
+ }
58
+
59
+ // ../blocks/src/system/runtime/nodes/booking-form.client.tsx
60
+ var import_react11 = __toESM(require("react"));
61
+
62
+ // ../blocks/src/system/runtime/components/multi-step/MultiStepForm.tsx
63
+ var import_react3 = require("react");
64
+
65
+ // ../blocks/src/system/runtime/components/multi-step/MultiStepContext.tsx
66
+ var import_react = require("react");
67
+ var MultiStepContext = (0, import_react.createContext)(null);
68
+ function useMultiStepContext() {
69
+ const context = (0, import_react.useContext)(MultiStepContext);
70
+ if (!context) {
71
+ throw new Error(
72
+ "useMultiStepContext must be used within a MultiStepForm component. Make sure your component is wrapped in <MultiStepForm>."
73
+ );
74
+ }
75
+ return context;
76
+ }
77
+ function useMultiStepData() {
78
+ const { data, updateData } = useMultiStepContext();
79
+ return { data, updateData };
80
+ }
81
+
82
+ // ../blocks/src/system/runtime/components/multi-step/useMultiStep.ts
83
+ var import_react2 = require("react");
84
+ function useMultiStep({
85
+ steps,
86
+ initialData = {},
87
+ onStepChange,
88
+ onDataChange,
89
+ onComplete,
90
+ persistToUrl = false
91
+ }) {
92
+ const [data, setData] = (0, import_react2.useState)(initialData);
93
+ const [currentStepIndex, setCurrentStepIndex] = (0, import_react2.useState)(0);
94
+ const [errors, setErrors] = (0, import_react2.useState)({});
95
+ const [isValidating, setIsValidating] = (0, import_react2.useState)(false);
96
+ const [isSubmitting, setIsSubmitting] = (0, import_react2.useState)(false);
97
+ const visibleSteps = (0, import_react2.useMemo)(() => {
98
+ return steps.filter((step) => {
99
+ if (!step.condition) return true;
100
+ return step.condition(data);
101
+ });
102
+ }, [steps, data]);
103
+ const currentStep = visibleSteps[currentStepIndex];
104
+ const currentStepId = currentStep?.id || "";
105
+ (0, import_react2.useEffect)(() => {
106
+ if (persistToUrl && currentStep) {
107
+ const url = new URL(window.location.href);
108
+ url.searchParams.set("step", currentStep.id);
109
+ window.history.replaceState({}, "", url.toString());
110
+ }
111
+ }, [currentStepIndex, currentStep, persistToUrl]);
112
+ (0, import_react2.useEffect)(() => {
113
+ if (persistToUrl) {
114
+ const url = new URL(window.location.href);
115
+ const stepId = url.searchParams.get("step");
116
+ if (stepId) {
117
+ const stepIndex = visibleSteps.findIndex((s) => s.id === stepId);
118
+ if (stepIndex !== -1) {
119
+ setCurrentStepIndex(stepIndex);
120
+ }
121
+ }
122
+ }
123
+ }, []);
124
+ (0, import_react2.useEffect)(() => {
125
+ if (onStepChange && currentStep) {
126
+ onStepChange(currentStep.id, currentStepIndex);
127
+ }
128
+ }, [currentStepIndex, currentStep, onStepChange]);
129
+ const updateData = (0, import_react2.useCallback)(
130
+ (updates) => {
131
+ setData((prev) => {
132
+ const newData = { ...prev, ...updates };
133
+ onDataChange?.(newData);
134
+ return newData;
135
+ });
136
+ setErrors({});
137
+ },
138
+ [onDataChange]
139
+ );
140
+ const validateCurrentStep = (0, import_react2.useCallback)(async () => {
141
+ if (!currentStep?.validate) return true;
142
+ setIsValidating(true);
143
+ setErrors({});
144
+ try {
145
+ const result = await currentStep.validate(data);
146
+ if (result.valid) {
147
+ return true;
148
+ } else {
149
+ setErrors(result.errors);
150
+ return false;
151
+ }
152
+ } catch (error) {
153
+ console.error("Step validation error:", error);
154
+ setErrors({ _form: "Validation failed. Please try again." });
155
+ return false;
156
+ } finally {
157
+ setIsValidating(false);
158
+ }
159
+ }, [currentStep, data]);
160
+ const goToNext = (0, import_react2.useCallback)(async () => {
161
+ const isValid = await validateCurrentStep();
162
+ if (!isValid) return;
163
+ if (currentStepIndex === visibleSteps.length - 1) {
164
+ if (onComplete) {
165
+ setIsSubmitting(true);
166
+ try {
167
+ await onComplete(data);
168
+ } catch (error) {
169
+ console.error("Form submission error:", error);
170
+ setErrors({ _form: "Submission failed. Please try again." });
171
+ } finally {
172
+ setIsSubmitting(false);
173
+ }
174
+ }
175
+ return;
176
+ }
177
+ setCurrentStepIndex((prev) => Math.min(prev + 1, visibleSteps.length - 1));
178
+ setErrors({});
179
+ }, [currentStepIndex, visibleSteps.length, validateCurrentStep, onComplete, data]);
180
+ const goToPrevious = (0, import_react2.useCallback)(() => {
181
+ setCurrentStepIndex((prev) => Math.max(prev - 1, 0));
182
+ setErrors({});
183
+ }, []);
184
+ const goToStep = (0, import_react2.useCallback)(
185
+ (stepIndex) => {
186
+ if (stepIndex >= 0 && stepIndex < visibleSteps.length) {
187
+ setCurrentStepIndex(stepIndex);
188
+ setErrors({});
189
+ }
190
+ },
191
+ [visibleSteps.length]
192
+ );
193
+ const goToStepById = (0, import_react2.useCallback)(
194
+ (stepId) => {
195
+ const stepIndex = visibleSteps.findIndex((s) => s.id === stepId);
196
+ if (stepIndex !== -1) {
197
+ goToStep(stepIndex);
198
+ }
199
+ },
200
+ [visibleSteps, goToStep]
201
+ );
202
+ const reset = (0, import_react2.useCallback)(() => {
203
+ setData(initialData);
204
+ setCurrentStepIndex(0);
205
+ setErrors({});
206
+ setIsValidating(false);
207
+ setIsSubmitting(false);
208
+ }, [initialData]);
209
+ const canGoNext = !isValidating && !isSubmitting;
210
+ const canGoBack = currentStepIndex > 0 && !isValidating && !isSubmitting;
211
+ const isFirstStep = currentStepIndex === 0;
212
+ const isLastStep = currentStepIndex === visibleSteps.length - 1;
213
+ return {
214
+ currentStepIndex,
215
+ currentStepId,
216
+ visibleSteps,
217
+ data,
218
+ errors,
219
+ isValidating,
220
+ isSubmitting,
221
+ goToNext,
222
+ goToPrevious,
223
+ goToStep,
224
+ goToStepById,
225
+ updateData,
226
+ canGoNext,
227
+ canGoBack,
228
+ isFirstStep,
229
+ isLastStep,
230
+ reset
231
+ };
232
+ }
233
+
234
+ // ../blocks/src/system/runtime/components/multi-step/MultiStepForm.tsx
235
+ var import_jsx_runtime = require("react/jsx-runtime");
236
+ function MultiStepForm({
237
+ steps,
238
+ initialData,
239
+ onComplete,
240
+ onStepChange,
241
+ onDataChange,
242
+ progressStyle = "dots",
243
+ className = "",
244
+ persistToUrl = false,
245
+ allowSkip = false
246
+ }) {
247
+ const context = useMultiStep({
248
+ steps,
249
+ initialData,
250
+ onStepChange,
251
+ onDataChange,
252
+ onComplete,
253
+ persistToUrl
254
+ });
255
+ const {
256
+ visibleSteps,
257
+ currentStepIndex,
258
+ goToNext,
259
+ goToPrevious,
260
+ goToStep,
261
+ canGoNext,
262
+ canGoBack,
263
+ isFirstStep,
264
+ isLastStep,
265
+ errors,
266
+ isValidating,
267
+ isSubmitting
268
+ } = context;
269
+ const currentStep = visibleSteps[currentStepIndex];
270
+ if (!currentStep) {
271
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-muted-foreground", children: "No steps available" }) });
272
+ }
273
+ const stepComponent = (0, import_react3.isValidElement)(currentStep.component) ? (0, import_react3.cloneElement)(currentStep.component, {
274
+ // Step components can access context via hooks, but we also pass key props
275
+ stepId: currentStep.id,
276
+ stepIndex: currentStepIndex
277
+ }) : currentStep.component;
278
+ const showBackButton = !isFirstStep && !currentStep.hideBack && canGoBack;
279
+ const showNextButton = !currentStep.isTerminal;
280
+ const nextButtonLabel = isLastStep ? currentStep.nextLabel || "Complete" : currentStep.nextLabel || "Continue";
281
+ const backButtonLabel = currentStep.backLabel || "Back";
282
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MultiStepContext.Provider, { value: context, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `multi-step-form ${className}`, children: [
283
+ progressStyle !== "none" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mb-8", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
284
+ ProgressIndicator,
285
+ {
286
+ steps: visibleSteps,
287
+ currentIndex: currentStepIndex,
288
+ style: progressStyle,
289
+ onStepClick: allowSkip ? goToStep : void 0
290
+ }
291
+ ) }),
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "step-content", children: stepComponent }),
293
+ Object.keys(errors).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-6 alert alert-error", children: [
294
+ errors._form && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm font-medium", children: errors._form }),
295
+ Object.entries(errors).filter(([key]) => key !== "_form").map(([key, message]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm", children: message }, key))
296
+ ] }),
297
+ (showBackButton || showNextButton) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3 mt-8", children: [
298
+ showBackButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
299
+ "button",
300
+ {
301
+ type: "button",
302
+ onClick: goToPrevious,
303
+ disabled: !canGoBack,
304
+ className: "outline btn-md",
305
+ children: backButtonLabel
306
+ }
307
+ ),
308
+ showNextButton && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
309
+ "button",
310
+ {
311
+ type: "button",
312
+ onClick: goToNext,
313
+ disabled: !canGoNext,
314
+ className: "primary btn-md flex-1",
315
+ children: [
316
+ isValidating && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-current" }),
317
+ isSubmitting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-current" }),
318
+ !isValidating && !isSubmitting && nextButtonLabel,
319
+ isValidating && "Validating...",
320
+ isSubmitting && "Submitting..."
321
+ ]
322
+ }
323
+ )
324
+ ] })
325
+ ] }) });
326
+ }
327
+ function ProgressIndicator({
328
+ steps,
329
+ currentIndex,
330
+ style,
331
+ onStepClick
332
+ }) {
333
+ if (style === "dots") {
334
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-center gap-2", children: steps.map((step, index) => {
335
+ const isActive = index === currentIndex;
336
+ const isCompleted = index < currentIndex;
337
+ const isClickable = onStepClick && index <= currentIndex;
338
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
339
+ "button",
340
+ {
341
+ type: "button",
342
+ onClick: isClickable ? () => onStepClick(index) : void 0,
343
+ disabled: !isClickable,
344
+ className: `
345
+ step-dot transition-all
346
+ ${isActive ? "step-dot-active" : ""}
347
+ ${isCompleted ? "step-dot-complete" : ""}
348
+ ${isClickable ? "cursor-pointer hover:scale-125" : "cursor-default"}
349
+ `,
350
+ "aria-label": step.title
351
+ },
352
+ step.id
353
+ );
354
+ }) });
355
+ }
356
+ if (style === "steps") {
357
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-between", children: steps.map((step, index) => {
358
+ const isActive = index === currentIndex;
359
+ const isCompleted = index < currentIndex;
360
+ const isClickable = onStepClick && index <= currentIndex;
361
+ const isLast = index === steps.length - 1;
362
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center flex-1", children: [
363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
364
+ "button",
365
+ {
366
+ type: "button",
367
+ onClick: isClickable ? () => onStepClick(index) : void 0,
368
+ disabled: !isClickable,
369
+ className: `
370
+ flex items-center gap-2
371
+ ${isClickable ? "cursor-pointer" : "cursor-default"}
372
+ `,
373
+ children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
375
+ "div",
376
+ {
377
+ className: `
378
+ step-circle transition-colors
379
+ ${isActive ? "step-circle-active" : ""}
380
+ ${isCompleted ? "step-circle-complete" : ""}
381
+ `,
382
+ children: isCompleted ? "\u2713" : index + 1
383
+ }
384
+ ),
385
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
386
+ "span",
387
+ {
388
+ className: `
389
+ text-sm hidden sm:inline
390
+ ${isActive ? "font-medium" : ""}
391
+ ${isCompleted || !isActive ? "status-muted" : ""}
392
+ `,
393
+ children: step.title
394
+ }
395
+ )
396
+ ]
397
+ }
398
+ ),
399
+ !isLast && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "step-connector mx-2 hidden sm:block" })
400
+ ] }, step.id);
401
+ }) });
402
+ }
403
+ const progress = (currentIndex + 1) / steps.length * 100;
404
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex justify-between text-sm status-muted", children: [
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
407
+ "Step ",
408
+ currentIndex + 1,
409
+ " of ",
410
+ steps.length
411
+ ] }),
412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
413
+ Math.round(progress),
414
+ "%"
415
+ ] })
416
+ ] }),
417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "progress-bar", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
418
+ "div",
419
+ {
420
+ className: "progress-fill",
421
+ style: { width: `${progress}%` }
422
+ }
423
+ ) })
424
+ ] });
425
+ }
426
+
427
+ // ../blocks/src/system/runtime/components/booking/SuccessMessage.tsx
428
+ var import_jsx_runtime2 = require("react/jsx-runtime");
429
+ var SuccessMessage = ({ message, className }) => {
430
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
431
+ "div",
432
+ {
433
+ className: `alert alert-success text-sm ${className ?? ""}`,
434
+ role: "alert",
435
+ "aria-live": "polite",
436
+ children: message
437
+ }
438
+ );
439
+ };
440
+
441
+ // ../blocks/src/system/runtime/hooks/useBookingSteps.ts
442
+ var import_react9 = __toESM(require("react"));
443
+
444
+ // ../blocks/src/system/runtime/components/booking/ServiceResourceSelector.tsx
445
+ var import_react4 = require("react");
446
+ var import_jsx_runtime3 = require("react/jsx-runtime");
447
+ function ServiceResourceSelector({
448
+ siteId,
449
+ services,
450
+ preSelectedServiceId,
451
+ preSelectedResourceId,
452
+ onSelect,
453
+ onBack,
454
+ heading = "Select Service",
455
+ description = "Choose the service you'd like to book"
456
+ }) {
457
+ const [selectedServiceId, setSelectedServiceId] = (0, import_react4.useState)(
458
+ preSelectedServiceId
459
+ );
460
+ const [selectedResourceId, setSelectedResourceId] = (0, import_react4.useState)(
461
+ preSelectedResourceId
462
+ );
463
+ const [resources, setResources] = (0, import_react4.useState)([]);
464
+ const [isLoadingResources, setIsLoadingResources] = (0, import_react4.useState)(false);
465
+ const [resourceError, setResourceError] = (0, import_react4.useState)(null);
466
+ const showServiceSelect = !preSelectedServiceId && services.length > 1;
467
+ const showResourceSelect = resources.length > 1 && !preSelectedResourceId;
468
+ const handleServiceChange = (0, import_react4.useCallback)(async (serviceId) => {
469
+ setSelectedServiceId(serviceId);
470
+ setSelectedResourceId(void 0);
471
+ setResourceError(null);
472
+ try {
473
+ setIsLoadingResources(true);
474
+ const apiUrl = getCmsApiUrl();
475
+ const response = await fetch(
476
+ `${apiUrl}/sites/${siteId}/bookings/resources/reference?serviceId=${serviceId}`
477
+ );
478
+ if (!response.ok) {
479
+ throw new Error("Failed to load resources");
480
+ }
481
+ const data = await response.json();
482
+ const loadedResources = data.options?.map((opt) => ({
483
+ id: opt.id,
484
+ displayName: opt.label
485
+ })) || [];
486
+ setResources(loadedResources);
487
+ } catch (err) {
488
+ console.error("Failed to load resources:", err);
489
+ setResourceError("Failed to load practitioners. Please try again.");
490
+ setResources([]);
491
+ } finally {
492
+ setIsLoadingResources(false);
493
+ }
494
+ }, [siteId]);
495
+ const handleContinue = () => {
496
+ if (selectedServiceId) {
497
+ onSelect({
498
+ serviceId: selectedServiceId,
499
+ resourceId: selectedResourceId === "__any__" ? void 0 : selectedResourceId
500
+ });
501
+ }
502
+ };
503
+ const canContinue = selectedServiceId && !isLoadingResources;
504
+ (0, import_react4.useEffect)(() => {
505
+ if (services.length === 1 && !selectedServiceId) {
506
+ handleServiceChange(services[0].id);
507
+ }
508
+ }, [services, selectedServiceId, handleServiceChange]);
509
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-6 max-w-2xl mx-auto", children: [
510
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
511
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "text-2xl font-bold", children: heading }),
512
+ description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-muted-foreground mt-2", children: description })
513
+ ] }),
514
+ showServiceSelect && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3", children: [
515
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Service" }),
516
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "grid gap-3", children: services.map((service) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
517
+ "button",
518
+ {
519
+ type: "button",
520
+ onClick: () => handleServiceChange(service.id),
521
+ className: `
522
+ relative p-4 rounded-lg border-2 text-left transition-all
523
+ ${selectedServiceId === service.id ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"}
524
+ `,
525
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between", children: [
526
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
527
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-medium", children: service.title }),
528
+ service.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-sm text-muted-foreground mt-1", children: service.description }),
529
+ service.durationMinutes && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-sm text-muted-foreground mt-1", children: [
530
+ service.durationMinutes,
531
+ " minutes"
532
+ ] })
533
+ ] }),
534
+ selectedServiceId === service.id && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-shrink-0 w-5 h-5 rounded-full bg-primary flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
535
+ "svg",
536
+ {
537
+ className: "w-3 h-3 text-primary-foreground",
538
+ fill: "none",
539
+ strokeLinecap: "round",
540
+ strokeLinejoin: "round",
541
+ strokeWidth: "2",
542
+ viewBox: "0 0 24 24",
543
+ stroke: "currentColor",
544
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5 13l4 4L19 7" })
545
+ }
546
+ ) })
547
+ ] })
548
+ },
549
+ service.id
550
+ )) })
551
+ ] }),
552
+ showResourceSelect && selectedServiceId && !isLoadingResources && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3", children: [
553
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Practitioner (Optional)" }),
554
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-muted-foreground", children: 'Choose a specific practitioner or select "Any Available"' }),
555
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "grid gap-3", children: [
556
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
557
+ "button",
558
+ {
559
+ type: "button",
560
+ onClick: () => setSelectedResourceId("__any__"),
561
+ className: `
562
+ relative p-4 rounded-lg border-2 text-left transition-all
563
+ ${selectedResourceId === "__any__" ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"}
564
+ `,
565
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
566
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-medium", children: "Any Available" }),
567
+ selectedResourceId === "__any__" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-shrink-0 w-5 h-5 rounded-full bg-primary flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
568
+ "svg",
569
+ {
570
+ className: "w-3 h-3 text-primary-foreground",
571
+ fill: "none",
572
+ strokeLinecap: "round",
573
+ strokeLinejoin: "round",
574
+ strokeWidth: "2",
575
+ viewBox: "0 0 24 24",
576
+ stroke: "currentColor",
577
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5 13l4 4L19 7" })
578
+ }
579
+ ) })
580
+ ] })
581
+ }
582
+ ),
583
+ resources.map((resource) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
584
+ "button",
585
+ {
586
+ type: "button",
587
+ onClick: () => setSelectedResourceId(resource.id),
588
+ className: `
589
+ relative p-4 rounded-lg border-2 text-left transition-all
590
+ ${selectedResourceId === resource.id ? "border-primary bg-primary/5" : "border-border hover:border-primary/50"}
591
+ `,
592
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between", children: [
593
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "font-medium", children: resource.displayName }),
594
+ selectedResourceId === resource.id && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-shrink-0 w-5 h-5 rounded-full bg-primary flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
595
+ "svg",
596
+ {
597
+ className: "w-3 h-3 text-primary-foreground",
598
+ fill: "none",
599
+ strokeLinecap: "round",
600
+ strokeLinejoin: "round",
601
+ strokeWidth: "2",
602
+ viewBox: "0 0 24 24",
603
+ stroke: "currentColor",
604
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5 13l4 4L19 7" })
605
+ }
606
+ ) })
607
+ ] })
608
+ },
609
+ resource.id
610
+ ))
611
+ ] })
612
+ ] }),
613
+ isLoadingResources && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center py-8", children: [
614
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary" }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-muted-foreground mt-2", children: "Loading practitioners..." })
616
+ ] }),
617
+ resourceError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "alert alert-error text-sm", children: resourceError }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex gap-3", children: [
619
+ onBack && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
620
+ "button",
621
+ {
622
+ type: "button",
623
+ onClick: onBack,
624
+ className: "px-6 py-2 rounded-lg border border-border hover:bg-muted transition-colors",
625
+ children: "Back"
626
+ }
627
+ ),
628
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
629
+ "button",
630
+ {
631
+ type: "button",
632
+ onClick: handleContinue,
633
+ disabled: !canContinue,
634
+ className: "flex-1 px-6 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
635
+ children: "Continue to Date Selection"
636
+ }
637
+ )
638
+ ] })
639
+ ] });
640
+ }
641
+
642
+ // ../blocks/src/system/runtime/components/booking/ServiceSelectionStep.tsx
643
+ var import_jsx_runtime4 = require("react/jsx-runtime");
644
+ function ServiceSelectionStep({
645
+ siteId,
646
+ services
647
+ }) {
648
+ const { updateData } = useMultiStepData();
649
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
650
+ ServiceResourceSelector,
651
+ {
652
+ siteId,
653
+ services,
654
+ onSelect: ({ serviceId, resourceId }) => {
655
+ updateData({ serviceId, resourceId });
656
+ }
657
+ }
658
+ );
659
+ }
660
+
661
+ // ../blocks/src/system/runtime/components/booking/DateTimeSelectionStep.tsx
662
+ var import_react7 = require("react");
663
+
664
+ // ../blocks/src/utils/date-formatting.ts
665
+ var BOOKING_FETCH_CHUNK_DAYS = 30;
666
+ function formatDate(dateStr) {
667
+ const date = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
668
+ return date.toLocaleDateString(void 0, {
669
+ weekday: "long",
670
+ year: "numeric",
671
+ month: "long",
672
+ day: "numeric"
673
+ });
674
+ }
675
+ function formatTime(isoString) {
676
+ const date = new Date(isoString);
677
+ return date.toLocaleTimeString(void 0, {
678
+ hour: "numeric",
679
+ minute: "2-digit",
680
+ timeZoneName: "short"
681
+ });
682
+ }
683
+
684
+ // ../blocks/src/system/runtime/components/booking/DatePicker.tsx
685
+ var import_jsx_runtime5 = require("react/jsx-runtime");
686
+ var DatePicker = ({
687
+ value,
688
+ onChange,
689
+ dateOptions,
690
+ required = true,
691
+ isLoading = false,
692
+ isEmpty = false,
693
+ hasMore = false,
694
+ onLoadMore
695
+ }) => {
696
+ if (isLoading) {
697
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-2", children: [
698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "form-label", children: [
699
+ "Select Date",
700
+ required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "required-marker", children: "*" })
701
+ ] }),
702
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "h-10 w-full animate-pulse rounded-control card-surface" }),
703
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs status-muted", children: "Loading available dates..." })
704
+ ] });
705
+ }
706
+ if (isEmpty) {
707
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-2", children: [
708
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "form-label", children: [
709
+ "Select Date",
710
+ required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "required-marker", children: "*" })
711
+ ] }),
712
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "alert alert-warning", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-sm", children: [
713
+ "No available dates in this period.",
714
+ hasMore && onLoadMore && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
715
+ "button",
716
+ {
717
+ type: "button",
718
+ onClick: onLoadMore,
719
+ className: "ml-2 underline hover:no-underline",
720
+ children: "Check later dates"
721
+ }
722
+ )
723
+ ] }) })
724
+ ] });
725
+ }
726
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-2", children: [
727
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { htmlFor: "booking-date", className: "form-label", children: [
728
+ "Select Date",
729
+ required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "required-marker", children: "*" })
730
+ ] }),
731
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
732
+ "select",
733
+ {
734
+ id: "booking-date",
735
+ value,
736
+ onChange: (e) => onChange(e.target.value),
737
+ required,
738
+ className: "form-select",
739
+ children: [
740
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", children: "Choose a date..." }),
741
+ dateOptions.map((date) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: date, children: formatDate(date) }, date))
742
+ ]
743
+ }
744
+ ),
745
+ hasMore && onLoadMore && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
746
+ "button",
747
+ {
748
+ type: "button",
749
+ onClick: onLoadMore,
750
+ className: "button-link text-sm",
751
+ children: "Load more dates \u2192"
752
+ }
753
+ )
754
+ ] });
755
+ };
756
+
757
+ // ../blocks/src/system/runtime/components/booking/TimeSlotSelector.tsx
758
+ var import_jsx_runtime6 = require("react/jsx-runtime");
759
+ var TimeSlotSelector = ({
760
+ value,
761
+ onChange,
762
+ slots,
763
+ isLoading,
764
+ required = true
765
+ }) => {
766
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-2", children: [
767
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { htmlFor: "booking-slot", className: "form-label", children: [
768
+ "Select Time",
769
+ required && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "required-marker", children: "*" })
770
+ ] }),
771
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "alert alert-info text-sm text-center", children: "Loading available times..." }) : slots.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "alert text-sm text-center status-muted", children: "No available times for this date" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
772
+ "select",
773
+ {
774
+ id: "booking-slot",
775
+ value,
776
+ onChange: (e) => onChange(e.target.value),
777
+ required,
778
+ className: "form-select",
779
+ children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "", children: "Choose a time..." }),
781
+ slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: slot.startAt, children: formatTime(slot.startAt) }, slot.startAt))
782
+ ]
783
+ }
784
+ )
785
+ ] });
786
+ };
787
+
788
+ // ../blocks/src/system/runtime/hooks/useAvailableSlots.ts
789
+ var import_react5 = require("react");
790
+ function useAvailableSlots({
791
+ siteId,
792
+ serviceId,
793
+ resourceId,
794
+ selectedDate,
795
+ timezone
796
+ }) {
797
+ const [slots, setSlots] = (0, import_react5.useState)([]);
798
+ const [isLoading, setIsLoading] = (0, import_react5.useState)(false);
799
+ const [error, setError] = (0, import_react5.useState)(null);
800
+ (0, import_react5.useEffect)(() => {
801
+ if (!selectedDate || !serviceId) {
802
+ setSlots([]);
803
+ return;
804
+ }
805
+ const fetchSlots = async () => {
806
+ setIsLoading(true);
807
+ setError(null);
808
+ setSlots([]);
809
+ try {
810
+ const params = new URLSearchParams({
811
+ siteId,
812
+ serviceId,
813
+ startDate: selectedDate,
814
+ endDate: selectedDate,
815
+ timezone
816
+ });
817
+ if (resourceId) {
818
+ params.append("resourceId", resourceId);
819
+ }
820
+ const apiUrl = getCmsApiUrl();
821
+ const response = await fetch(`${apiUrl}/public/bookings/availability/slots?${params}`, {
822
+ method: "GET",
823
+ headers: {
824
+ "Content-Type": "application/json"
825
+ }
826
+ });
827
+ if (!response.ok) {
828
+ const errorData = await response.json().catch(() => ({}));
829
+ throw new Error(errorData.error || "Failed to load available times");
830
+ }
831
+ const data = await response.json();
832
+ setSlots(data.slots);
833
+ } catch (err) {
834
+ setError(err instanceof Error ? err.message : "Failed to load available times");
835
+ } finally {
836
+ setIsLoading(false);
837
+ }
838
+ };
839
+ fetchSlots();
840
+ }, [selectedDate, serviceId, resourceId, timezone, siteId]);
841
+ return { slots, isLoading, error };
842
+ }
843
+
844
+ // ../blocks/src/system/runtime/hooks/useAvailableDates.ts
845
+ var import_react6 = require("react");
846
+ function useAvailableDates({
847
+ siteId,
848
+ serviceId,
849
+ resourceId,
850
+ timezone = "UTC",
851
+ initialDays = 30
852
+ }) {
853
+ const [availableDates, setAvailableDates] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
854
+ const [isLoading, setIsLoading] = (0, import_react6.useState)(false);
855
+ const [error, setError] = (0, import_react6.useState)(null);
856
+ const [hasMore, setHasMore] = (0, import_react6.useState)(false);
857
+ const [loadedRange, setLoadedRange] = (0, import_react6.useState)(null);
858
+ const nextStartDateRef = (0, import_react6.useRef)(null);
859
+ const requestIdRef = (0, import_react6.useRef)(0);
860
+ const fetchDates = (0, import_react6.useCallback)(
861
+ async (startDate, endDate, append = false) => {
862
+ if (!serviceId) {
863
+ console.log("[useAvailableDates] No serviceId, skipping fetch");
864
+ return;
865
+ }
866
+ const requestId = ++requestIdRef.current;
867
+ setIsLoading(true);
868
+ setError(null);
869
+ try {
870
+ const apiUrl = getCmsApiUrl();
871
+ const params = new URLSearchParams({
872
+ siteId,
873
+ serviceId,
874
+ startDate,
875
+ endDate,
876
+ timezone
877
+ });
878
+ if (resourceId) {
879
+ params.append("resourceId", resourceId);
880
+ }
881
+ const url = `${apiUrl}/public/bookings/availability/dates?${params}`;
882
+ console.log("[useAvailableDates] Fetching:", url);
883
+ const res = await fetch(url, {
884
+ method: "GET",
885
+ headers: {
886
+ "Content-Type": "application/json"
887
+ }
888
+ });
889
+ console.log("[useAvailableDates] Response status:", res.status);
890
+ if (requestId !== requestIdRef.current) return;
891
+ if (!res.ok) {
892
+ const errorData = await res.json().catch(() => ({}));
893
+ console.log("[useAvailableDates] Error response:", errorData);
894
+ throw new Error(errorData.error || "Failed to fetch available dates");
895
+ }
896
+ const data = await res.json();
897
+ console.log("[useAvailableDates] Received dates:", data);
898
+ setAvailableDates((prev) => {
899
+ const newSet = append ? new Set(prev) : /* @__PURE__ */ new Set();
900
+ data.dates.forEach((d) => newSet.add(d));
901
+ console.log("[useAvailableDates] Updated availableDates Set size:", newSet.size);
902
+ return newSet;
903
+ });
904
+ setHasMore(data.hasMore);
905
+ nextStartDateRef.current = data.nextStartDate || null;
906
+ setLoadedRange((prev) => ({
907
+ start: append && prev ? prev.start : data.startDate,
908
+ end: data.endDate
909
+ }));
910
+ } catch (err) {
911
+ if (requestId !== requestIdRef.current) return;
912
+ setError(err instanceof Error ? err.message : "Failed to load available dates");
913
+ } finally {
914
+ if (requestId === requestIdRef.current) {
915
+ setIsLoading(false);
916
+ }
917
+ }
918
+ },
919
+ [siteId, serviceId, resourceId, timezone]
920
+ );
921
+ (0, import_react6.useEffect)(() => {
922
+ if (!serviceId) {
923
+ setAvailableDates(/* @__PURE__ */ new Set());
924
+ setLoadedRange(null);
925
+ setHasMore(false);
926
+ return;
927
+ }
928
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
929
+ const endDate = new Date(Date.now() + initialDays * 24 * 60 * 60 * 1e3).toISOString().split("T")[0];
930
+ fetchDates(today, endDate, false);
931
+ }, [serviceId, fetchDates, initialDays]);
932
+ const loadMore = (0, import_react6.useCallback)(() => {
933
+ if (!nextStartDateRef.current || isLoading) return;
934
+ const start = nextStartDateRef.current;
935
+ const msPerDay = 24 * 60 * 60 * 1e3;
936
+ const end = new Date(new Date(start).getTime() + BOOKING_FETCH_CHUNK_DAYS * msPerDay).toISOString().split("T")[0];
937
+ fetchDates(start, end, true);
938
+ }, [fetchDates, isLoading]);
939
+ return {
940
+ availableDates,
941
+ isLoading,
942
+ error,
943
+ hasMore,
944
+ loadMore,
945
+ loadedRange
946
+ };
947
+ }
948
+
949
+ // ../blocks/src/system/runtime/components/booking/DateTimeSelectionStep.tsx
950
+ var import_jsx_runtime7 = require("react/jsx-runtime");
951
+ function DateTimeSelectionStep({
952
+ siteId,
953
+ preSelectedServiceId,
954
+ preSelectedResourceId,
955
+ singleService
956
+ }) {
957
+ const { data, updateData } = useMultiStepData();
958
+ const serviceId = data.serviceId || preSelectedServiceId;
959
+ const resourceId = data.resourceId || preSelectedResourceId;
960
+ const selectedDate = data.selectedDate || "";
961
+ const selectedSlot = data.selectedSlot || "";
962
+ (0, import_react7.useEffect)(() => {
963
+ if (preSelectedServiceId && !data.serviceId) {
964
+ updateData({ serviceId: preSelectedServiceId });
965
+ }
966
+ }, [preSelectedServiceId, data.serviceId, updateData]);
967
+ const {
968
+ availableDates,
969
+ isLoading: isLoadingDates,
970
+ error: datesError,
971
+ hasMore,
972
+ loadMore
973
+ } = useAvailableDates({
974
+ siteId,
975
+ serviceId,
976
+ resourceId,
977
+ timezone: "UTC",
978
+ initialDays: 30
979
+ });
980
+ const { slots, isLoading: isLoadingSlots, error: slotsError } = useAvailableSlots({
981
+ siteId,
982
+ serviceId,
983
+ resourceId,
984
+ selectedDate,
985
+ timezone: "UTC"
986
+ });
987
+ const filteredDateOptions = (0, import_react7.useMemo)(() => {
988
+ if (isLoadingDates || availableDates.size === 0) {
989
+ return [];
990
+ }
991
+ return [...availableDates].sort();
992
+ }, [availableDates, isLoadingDates]);
993
+ const handleDateChange = (date) => {
994
+ updateData({ selectedDate: date, selectedSlot: "" });
995
+ };
996
+ const handleSlotChange = (slot) => {
997
+ updateData({ selectedSlot: slot });
998
+ };
999
+ const error = datesError || slotsError;
1000
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-6", children: [
1001
+ singleService && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rounded-lg border border-border bg-muted/30 p-4", children: [
1002
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "font-medium text-foreground", children: singleService.title }),
1003
+ singleService.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "mt-1 text-sm text-muted-foreground", children: singleService.description }),
1004
+ singleService.durationMinutes && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { className: "mt-1 text-sm text-muted-foreground", children: [
1005
+ "Duration: ",
1006
+ singleService.durationMinutes,
1007
+ " minutes"
1008
+ ] })
1009
+ ] }),
1010
+ error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "alert alert-error text-sm", children: error }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1012
+ DatePicker,
1013
+ {
1014
+ value: selectedDate,
1015
+ onChange: handleDateChange,
1016
+ dateOptions: filteredDateOptions,
1017
+ isLoading: isLoadingDates,
1018
+ isEmpty: !isLoadingDates && filteredDateOptions.length === 0,
1019
+ hasMore,
1020
+ onLoadMore: loadMore
1021
+ }
1022
+ ),
1023
+ selectedDate && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1024
+ TimeSlotSelector,
1025
+ {
1026
+ value: selectedSlot,
1027
+ onChange: handleSlotChange,
1028
+ slots,
1029
+ isLoading: isLoadingSlots
1030
+ }
1031
+ )
1032
+ ] });
1033
+ }
1034
+
1035
+ // ../blocks/src/system/runtime/components/multi-step/DynamicFormFields.tsx
1036
+ var import_react8 = require("react");
1037
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1038
+ function DynamicFormFields({
1039
+ fields,
1040
+ showLabels = true,
1041
+ className = ""
1042
+ }) {
1043
+ const { data, updateData } = useMultiStepData();
1044
+ const [touched, setTouched] = (0, import_react8.useState)({});
1045
+ const handleChange = (0, import_react8.useCallback)(
1046
+ (fieldId, value) => {
1047
+ updateData({ [fieldId]: value });
1048
+ },
1049
+ [updateData]
1050
+ );
1051
+ const handleBlur = (0, import_react8.useCallback)((fieldId) => {
1052
+ setTouched((prev) => ({ ...prev, [fieldId]: true }));
1053
+ }, []);
1054
+ const getFieldError = (0, import_react8.useCallback)(
1055
+ (field) => {
1056
+ const value = data[field.id];
1057
+ const isTouched = touched[field.id];
1058
+ if (!isTouched) return null;
1059
+ if (field.required && !value) {
1060
+ return `${field.label} is required`;
1061
+ }
1062
+ if (value) {
1063
+ switch (field.type) {
1064
+ case "email":
1065
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
1066
+ return "Please enter a valid email address";
1067
+ }
1068
+ break;
1069
+ case "url":
1070
+ try {
1071
+ new URL(value);
1072
+ } catch {
1073
+ return "Please enter a valid URL";
1074
+ }
1075
+ break;
1076
+ case "tel":
1077
+ if (!/^[+\d\s\-()]+$/.test(value)) {
1078
+ return "Please enter a valid phone number";
1079
+ }
1080
+ break;
1081
+ case "number":
1082
+ const num = Number(value);
1083
+ if (isNaN(num)) {
1084
+ return "Please enter a valid number";
1085
+ }
1086
+ if (field.min !== void 0 && num < field.min) {
1087
+ return `Value must be at least ${field.min}`;
1088
+ }
1089
+ if (field.max !== void 0 && num > field.max) {
1090
+ return `Value must be at most ${field.max}`;
1091
+ }
1092
+ break;
1093
+ }
1094
+ if (typeof value === "string") {
1095
+ if (field.minLength && value.length < field.minLength) {
1096
+ return `Must be at least ${field.minLength} characters`;
1097
+ }
1098
+ if (field.maxLength && value.length > field.maxLength) {
1099
+ return `Must be at most ${field.maxLength} characters`;
1100
+ }
1101
+ }
1102
+ if (field.pattern && typeof value === "string") {
1103
+ const regex = new RegExp(field.pattern);
1104
+ if (!regex.test(value)) {
1105
+ return "Please match the required format";
1106
+ }
1107
+ }
1108
+ }
1109
+ return null;
1110
+ },
1111
+ [data, touched]
1112
+ );
1113
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: `space-y-6 ${className}`, children: fields.map((field) => {
1114
+ const error = getFieldError(field);
1115
+ const value = data[field.id] ?? "";
1116
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-2", children: [
1117
+ showLabels && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { htmlFor: field.id, className: "form-label", children: [
1118
+ field.label,
1119
+ field.required && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "required-marker", children: "*" })
1120
+ ] }),
1121
+ field.helpText && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm status-muted", children: field.helpText }),
1122
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1123
+ FieldInput,
1124
+ {
1125
+ field,
1126
+ value,
1127
+ onChange: (val) => handleChange(field.id, val),
1128
+ onBlur: () => handleBlur(field.id),
1129
+ error
1130
+ }
1131
+ ),
1132
+ error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm status-error", children: error })
1133
+ ] }, field.id);
1134
+ }) });
1135
+ }
1136
+ function FieldInput({
1137
+ field,
1138
+ value,
1139
+ onChange,
1140
+ onBlur,
1141
+ error
1142
+ }) {
1143
+ const inputClass = "form-input";
1144
+ const textareaClass = "form-textarea";
1145
+ const selectClass = "form-select";
1146
+ const checkboxClass = "form-checkbox";
1147
+ const radioClass = "form-radio";
1148
+ switch (field.type) {
1149
+ case "textarea":
1150
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1151
+ "textarea",
1152
+ {
1153
+ id: field.id,
1154
+ name: field.id,
1155
+ value,
1156
+ onChange: (e) => onChange(e.target.value),
1157
+ onBlur,
1158
+ placeholder: field.placeholder,
1159
+ required: field.required,
1160
+ minLength: field.minLength,
1161
+ maxLength: field.maxLength,
1162
+ rows: 4,
1163
+ "aria-invalid": error ? "true" : void 0,
1164
+ className: textareaClass
1165
+ }
1166
+ );
1167
+ case "select":
1168
+ if (field.multiple) {
1169
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1170
+ "select",
1171
+ {
1172
+ id: field.id,
1173
+ name: field.id,
1174
+ value: Array.isArray(value) ? value : [],
1175
+ onChange: (e) => {
1176
+ const selected = Array.from(e.target.selectedOptions, (opt) => opt.value);
1177
+ onChange(selected);
1178
+ },
1179
+ onBlur,
1180
+ required: field.required,
1181
+ multiple: true,
1182
+ "aria-invalid": error ? "true" : void 0,
1183
+ className: `${selectClass} h-32`,
1184
+ children: field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
1185
+ }
1186
+ );
1187
+ }
1188
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1189
+ "select",
1190
+ {
1191
+ id: field.id,
1192
+ name: field.id,
1193
+ value,
1194
+ onChange: (e) => onChange(e.target.value),
1195
+ onBlur,
1196
+ required: field.required,
1197
+ "aria-invalid": error ? "true" : void 0,
1198
+ className: selectClass,
1199
+ children: [
1200
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "", children: "Select an option..." }),
1201
+ field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
1202
+ ]
1203
+ }
1204
+ );
1205
+ case "radio":
1206
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "space-y-2", children: field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer", children: [
1207
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1208
+ "input",
1209
+ {
1210
+ type: "radio",
1211
+ name: field.id,
1212
+ value: opt.value,
1213
+ checked: value === opt.value,
1214
+ onChange: (e) => onChange(e.target.value),
1215
+ onBlur,
1216
+ required: field.required,
1217
+ className: radioClass
1218
+ }
1219
+ ),
1220
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-sm", children: opt.label })
1221
+ ] }, opt.value)) });
1222
+ case "checkbox":
1223
+ if (field.options && field.options.length > 1) {
1224
+ const checkedValues = Array.isArray(value) ? value : [];
1225
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "space-y-2", children: field.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer", children: [
1226
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1227
+ "input",
1228
+ {
1229
+ type: "checkbox",
1230
+ name: field.id,
1231
+ value: opt.value,
1232
+ checked: checkedValues.includes(opt.value),
1233
+ onChange: (e) => {
1234
+ const newValues = e.target.checked ? [...checkedValues, opt.value] : checkedValues.filter((v) => v !== opt.value);
1235
+ onChange(newValues);
1236
+ },
1237
+ onBlur,
1238
+ className: checkboxClass
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-sm", children: opt.label })
1242
+ ] }, opt.value)) });
1243
+ }
1244
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer", children: [
1245
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1246
+ "input",
1247
+ {
1248
+ type: "checkbox",
1249
+ id: field.id,
1250
+ name: field.id,
1251
+ checked: !!value,
1252
+ onChange: (e) => onChange(e.target.checked),
1253
+ onBlur,
1254
+ required: field.required,
1255
+ className: checkboxClass
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-sm", children: field.placeholder || field.label })
1259
+ ] });
1260
+ case "consent":
1261
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex items-start gap-2 cursor-pointer", children: [
1262
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1263
+ "input",
1264
+ {
1265
+ type: "checkbox",
1266
+ id: field.id,
1267
+ name: field.id,
1268
+ checked: !!value,
1269
+ onChange: (e) => onChange(e.target.checked),
1270
+ onBlur,
1271
+ required: field.required,
1272
+ className: `${checkboxClass} mt-0.5`
1273
+ }
1274
+ ),
1275
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-sm", children: field.placeholder || field.label })
1276
+ ] });
1277
+ case "number":
1278
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1279
+ "input",
1280
+ {
1281
+ type: "number",
1282
+ id: field.id,
1283
+ name: field.id,
1284
+ value,
1285
+ onChange: (e) => onChange(e.target.value),
1286
+ onBlur,
1287
+ placeholder: field.placeholder,
1288
+ required: field.required,
1289
+ min: field.min,
1290
+ max: field.max,
1291
+ "aria-invalid": error ? "true" : void 0,
1292
+ className: inputClass
1293
+ }
1294
+ );
1295
+ case "date":
1296
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1297
+ "input",
1298
+ {
1299
+ type: "date",
1300
+ id: field.id,
1301
+ name: field.id,
1302
+ value,
1303
+ onChange: (e) => onChange(e.target.value),
1304
+ onBlur,
1305
+ required: field.required,
1306
+ min: field.min ? String(field.min) : void 0,
1307
+ max: field.max ? String(field.max) : void 0,
1308
+ "aria-invalid": error ? "true" : void 0,
1309
+ className: inputClass
1310
+ }
1311
+ );
1312
+ case "time":
1313
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1314
+ "input",
1315
+ {
1316
+ type: "time",
1317
+ id: field.id,
1318
+ name: field.id,
1319
+ value,
1320
+ onChange: (e) => onChange(e.target.value),
1321
+ onBlur,
1322
+ required: field.required,
1323
+ "aria-invalid": error ? "true" : void 0,
1324
+ className: inputClass
1325
+ }
1326
+ );
1327
+ // text, email, tel, url
1328
+ default:
1329
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1330
+ "input",
1331
+ {
1332
+ type: field.type,
1333
+ id: field.id,
1334
+ name: field.id,
1335
+ value,
1336
+ onChange: (e) => onChange(e.target.value),
1337
+ onBlur,
1338
+ placeholder: field.placeholder,
1339
+ required: field.required,
1340
+ minLength: field.minLength,
1341
+ maxLength: field.maxLength,
1342
+ pattern: field.pattern,
1343
+ "aria-invalid": error ? "true" : void 0,
1344
+ className: inputClass
1345
+ }
1346
+ );
1347
+ }
1348
+ }
1349
+
1350
+ // ../blocks/src/system/runtime/hooks/useBookingSteps.ts
1351
+ function useBookingSteps(siteId, formConfig, services) {
1352
+ return (0, import_react9.useMemo)(() => {
1353
+ if (!formConfig) return [];
1354
+ const stepsArray = [];
1355
+ const effectiveServiceId = formConfig.settings?.serviceId || (services.length === 1 ? services[0].id : void 0);
1356
+ const needsServiceSelection = !formConfig.settings?.serviceId && services.length > 1 || formConfig.settings?.serviceIds && formConfig.settings.serviceIds.length > 1;
1357
+ if (needsServiceSelection) {
1358
+ stepsArray.push({
1359
+ id: "service-selection",
1360
+ title: "Select Service",
1361
+ component: import_react9.default.createElement(ServiceSelectionStep, { siteId, services }),
1362
+ condition: () => needsServiceSelection,
1363
+ validate: (data) => {
1364
+ if (!data.serviceId) {
1365
+ return { valid: false, errors: { _form: "Please select a service" } };
1366
+ }
1367
+ return { valid: true };
1368
+ }
1369
+ });
1370
+ }
1371
+ const singleService = services.length === 1 ? services[0] : void 0;
1372
+ stepsArray.push({
1373
+ id: "datetime-selection",
1374
+ title: "Select Date & Time",
1375
+ component: import_react9.default.createElement(DateTimeSelectionStep, {
1376
+ siteId,
1377
+ preSelectedServiceId: effectiveServiceId,
1378
+ preSelectedResourceId: formConfig.settings?.resourceId,
1379
+ singleService
1380
+ }),
1381
+ validate: (data) => {
1382
+ if (!data.selectedSlot) {
1383
+ return { valid: false, errors: { _form: "Please select a date and time" } };
1384
+ }
1385
+ return { valid: true };
1386
+ }
1387
+ });
1388
+ if (formConfig.schema?.fields && formConfig.schema.fields.length > 0) {
1389
+ stepsArray.push({
1390
+ id: "custom-fields",
1391
+ title: "Your Information",
1392
+ component: import_react9.default.createElement(DynamicFormFields, { fields: formConfig.schema.fields }),
1393
+ validate: (data) => {
1394
+ const errors = {};
1395
+ for (const field of formConfig.schema.fields) {
1396
+ if (field.required && !data[field.id]) {
1397
+ errors[field.id] = `${field.label} is required`;
1398
+ }
1399
+ }
1400
+ if (Object.keys(errors).length > 0) {
1401
+ return { valid: false, errors };
1402
+ }
1403
+ return { valid: true };
1404
+ }
1405
+ });
1406
+ }
1407
+ return stepsArray;
1408
+ }, [formConfig, services, siteId]);
1409
+ }
1410
+
1411
+ // ../blocks/src/system/runtime/hooks/useBookingSubmission.ts
1412
+ var import_react10 = require("react");
1413
+ function useBookingSubmission(siteId) {
1414
+ const [isSubmitting, setIsSubmitting] = (0, import_react10.useState)(false);
1415
+ const [error, setError] = (0, import_react10.useState)(null);
1416
+ const [isSuccess, setIsSuccess] = (0, import_react10.useState)(false);
1417
+ const submit = async (data) => {
1418
+ setIsSubmitting(true);
1419
+ setError(null);
1420
+ try {
1421
+ const apiUrl = getCmsApiUrl();
1422
+ const response = await fetch(`${apiUrl}/public/bookings/appointments`, {
1423
+ method: "POST",
1424
+ headers: {
1425
+ "Content-Type": "application/json"
1426
+ },
1427
+ body: JSON.stringify({
1428
+ formId: data.formId,
1429
+ serviceId: data.serviceId,
1430
+ resourceId: data.resourceId || null,
1431
+ startAt: data.startAt,
1432
+ endAt: data.endAt,
1433
+ customFields: data.customFields,
1434
+ timezone: data.timezone
1435
+ })
1436
+ });
1437
+ if (!response.ok) {
1438
+ const errorData = await response.json().catch(() => ({}));
1439
+ throw new Error(errorData.error || "Failed to book appointment");
1440
+ }
1441
+ setIsSuccess(true);
1442
+ } catch (err) {
1443
+ setError(err instanceof Error ? err.message : "Failed to book appointment");
1444
+ throw err;
1445
+ } finally {
1446
+ setIsSubmitting(false);
1447
+ }
1448
+ };
1449
+ return { submit, isSubmitting, error, isSuccess };
1450
+ }
1451
+
1452
+ // ../blocks/src/system/runtime/nodes/booking-form.client.tsx
1453
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1454
+ var BookingFormClient = ({
1455
+ siteId: siteIdProp,
1456
+ formId,
1457
+ className,
1458
+ form,
1459
+ services = []
1460
+ }) => {
1461
+ const [isSuccess, setIsSuccess] = (0, import_react11.useState)(false);
1462
+ const siteId = siteIdProp || form?.siteId || "";
1463
+ import_react11.default.useEffect(() => {
1464
+ if (!siteId) {
1465
+ console.error("[BookingFormClient] ERROR: siteId is missing!");
1466
+ }
1467
+ if (!form) {
1468
+ console.warn("[BookingFormClient] Form data not loaded");
1469
+ }
1470
+ console.log("[BookingFormClient] Loaded with:", {
1471
+ siteId,
1472
+ formId,
1473
+ hasForm: !!form,
1474
+ servicesCount: Array.isArray(services) ? services.length : "not an array",
1475
+ servicesData: services
1476
+ });
1477
+ }, [siteId, formId, form, services]);
1478
+ const formConfig = form ? {
1479
+ id: form.id,
1480
+ name: form.name,
1481
+ slug: "",
1482
+ // Not needed for booking form
1483
+ settings: form.settingsJson,
1484
+ schema: form.schemaJson
1485
+ } : null;
1486
+ const normalizedServices = import_react11.default.useMemo(() => {
1487
+ if (Array.isArray(services)) {
1488
+ return services;
1489
+ }
1490
+ if (services && typeof services === "object") {
1491
+ if ("services" in services) {
1492
+ return services.services || [];
1493
+ }
1494
+ return Object.values(services);
1495
+ }
1496
+ return [];
1497
+ }, [services]);
1498
+ const steps = useBookingSteps(siteId, formConfig, normalizedServices);
1499
+ const { submit } = useBookingSubmission(siteId);
1500
+ if (!form) {
1501
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `rounded-lg border border-destructive bg-destructive/10 p-4 ${className ?? ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-sm text-destructive", children: "Booking form not found. Please check your configuration." }) });
1502
+ }
1503
+ const handleComplete = async (data) => {
1504
+ try {
1505
+ const serviceId = data.serviceId || formConfig?.settings?.serviceId;
1506
+ const resourceId = data.resourceId || formConfig?.settings?.resourceId;
1507
+ if (!serviceId || !data.selectedSlot) {
1508
+ throw new Error("Missing required booking information");
1509
+ }
1510
+ console.log("[BookingFormClient] Looking for service:", {
1511
+ serviceId,
1512
+ availableServices: normalizedServices
1513
+ });
1514
+ const selectedService = normalizedServices.find((s) => s.id === serviceId);
1515
+ console.log("[BookingFormClient] Selected service:", selectedService);
1516
+ if (!selectedService) {
1517
+ throw new Error(`Service not found: ${serviceId}`);
1518
+ }
1519
+ if (!selectedService.durationMinutes) {
1520
+ throw new Error(
1521
+ `Service "${selectedService.title || serviceId}" is missing duration. Please update the service in the dashboard to include a duration (e.g., 30 minutes).`
1522
+ );
1523
+ }
1524
+ const startDate = new Date(data.selectedSlot);
1525
+ const endDate = new Date(startDate.getTime() + selectedService.durationMinutes * 60 * 1e3);
1526
+ const bookingFields = /* @__PURE__ */ new Set(["serviceId", "resourceId", "selectedDate", "selectedSlot"]);
1527
+ const customFields = {};
1528
+ for (const [key, value] of Object.entries(data)) {
1529
+ if (!bookingFields.has(key)) {
1530
+ customFields[key] = value;
1531
+ }
1532
+ }
1533
+ await submit({
1534
+ formId,
1535
+ serviceId,
1536
+ resourceId,
1537
+ startAt: data.selectedSlot,
1538
+ endAt: endDate.toISOString(),
1539
+ customFields,
1540
+ timezone: "UTC"
1541
+ // TODO: Get from resource or user preference
1542
+ });
1543
+ setIsSuccess(true);
1544
+ } catch (err) {
1545
+ console.error("Booking submission failed:", err);
1546
+ throw err;
1547
+ }
1548
+ };
1549
+ if (isSuccess) {
1550
+ const successMessage = formConfig?.settings?.successMessage || "Your appointment has been booked! Check your email for confirmation.";
1551
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SuccessMessage, { message: successMessage, className });
1552
+ }
1553
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1554
+ MultiStepForm,
1555
+ {
1556
+ steps,
1557
+ initialData: {
1558
+ serviceId: formConfig?.settings?.serviceId,
1559
+ resourceId: formConfig?.settings?.resourceId
1560
+ },
1561
+ onComplete: handleComplete,
1562
+ progressStyle: "steps",
1563
+ persistToUrl: false,
1564
+ allowSkip: false
1565
+ }
1566
+ ) });
1567
+ };
1568
+
1569
+ // ../blocks/src/system/runtime/hooks/useBookingFormConfig.ts
1570
+ var import_react12 = require("react");
1571
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1572
+ function useBookingFormConfig(siteId, formId) {
1573
+ const [formConfig, setFormConfig] = (0, import_react12.useState)(null);
1574
+ const [services, setServices] = (0, import_react12.useState)([]);
1575
+ const [isLoading, setIsLoading] = (0, import_react12.useState)(true);
1576
+ const [error, setError] = (0, import_react12.useState)(null);
1577
+ (0, import_react12.useEffect)(() => {
1578
+ let mounted = true;
1579
+ async function loadFormAndServices() {
1580
+ try {
1581
+ setIsLoading(true);
1582
+ setError(null);
1583
+ const apiUrl = getCmsApiUrl();
1584
+ const isUUID = UUID_REGEX.test(formId);
1585
+ const formUrl = isUUID ? `${apiUrl}/public/forms/${formId}` : `${apiUrl}/public/forms/${formId}?siteId=${encodeURIComponent(siteId)}`;
1586
+ const response = await fetch(formUrl);
1587
+ if (!response.ok) {
1588
+ throw new Error("Failed to load booking form");
1589
+ }
1590
+ const { form: rawData } = await response.json();
1591
+ if (!mounted) return;
1592
+ const normalizedForm = {
1593
+ id: rawData.id,
1594
+ name: rawData.name,
1595
+ slug: rawData.slug,
1596
+ settings: rawData.settingsJson ?? rawData.settings,
1597
+ schema: rawData.schemaJson ?? rawData.schema
1598
+ };
1599
+ setFormConfig(normalizedForm);
1600
+ if (!normalizedForm.settings?.serviceId && !normalizedForm.settings?.serviceIds) {
1601
+ const servicesResponse = await fetch(
1602
+ `${apiUrl}/public/bookings/services?siteId=${encodeURIComponent(siteId)}`
1603
+ );
1604
+ if (servicesResponse.ok) {
1605
+ const servicesData = await servicesResponse.json();
1606
+ if (mounted) {
1607
+ setServices(servicesData.services || []);
1608
+ }
1609
+ }
1610
+ } else if (normalizedForm.settings?.serviceIds && normalizedForm.settings.serviceIds.length > 0) {
1611
+ const servicesResponse = await fetch(
1612
+ `${apiUrl}/public/bookings/services?siteId=${encodeURIComponent(siteId)}&ids=${normalizedForm.settings.serviceIds.join(",")}`
1613
+ );
1614
+ if (servicesResponse.ok) {
1615
+ const servicesData = await servicesResponse.json();
1616
+ if (mounted) {
1617
+ setServices(servicesData.services || []);
1618
+ }
1619
+ }
1620
+ } else if (normalizedForm.settings?.serviceId) {
1621
+ const serviceResponse = await fetch(
1622
+ `${apiUrl}/public/bookings/services/${normalizedForm.settings.serviceId}?siteId=${encodeURIComponent(siteId)}`
1623
+ );
1624
+ if (serviceResponse.ok) {
1625
+ const serviceData = await serviceResponse.json();
1626
+ if (mounted) {
1627
+ setServices([serviceData.service || serviceData]);
1628
+ }
1629
+ }
1630
+ }
1631
+ } catch (err) {
1632
+ console.error("Failed to load form:", err);
1633
+ if (mounted) {
1634
+ setError("Failed to load booking form. Please try again later.");
1635
+ }
1636
+ } finally {
1637
+ if (mounted) {
1638
+ setIsLoading(false);
1639
+ }
1640
+ }
1641
+ }
1642
+ loadFormAndServices();
1643
+ return () => {
1644
+ mounted = false;
1645
+ };
1646
+ }, [siteId, formId]);
1647
+ return { formConfig, services, isLoading, error };
1648
+ }
29
1649
  // Annotate the CommonJS export names for ESM import in node:
30
1650
  0 && (module.exports = {
31
1651
  BookingFormClient,