@idkwebsites/components 0.1.7 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -136,29 +136,97 @@ function useTenant() {
136
136
  }
137
137
 
138
138
  // src/core/hooks/useServices.ts
139
+ var import_react3 = require("react");
139
140
  var import_react_query3 = require("@tanstack/react-query");
140
- function useServices() {
141
+ function buildQuery(params) {
142
+ if (!params) return "";
143
+ const searchParams = new URLSearchParams();
144
+ const categories = Array.isArray(params.category) ? params.category : params.category ? [params.category] : [];
145
+ if (categories.length) searchParams.set("category", categories.join(","));
146
+ if (params.staffId) searchParams.set("staffId", params.staffId);
147
+ if (typeof params.isActive === "boolean") searchParams.set("isActive", String(params.isActive));
148
+ if (typeof params.minPrice === "number") searchParams.set("minPrice", String(params.minPrice));
149
+ if (typeof params.maxPrice === "number") searchParams.set("maxPrice", String(params.maxPrice));
150
+ if (params.search) searchParams.set("search", params.search);
151
+ if (params.ids?.length) searchParams.set("ids", params.ids.join(","));
152
+ if (typeof params.limit === "number") searchParams.set("limit", String(params.limit));
153
+ if (params.sort) searchParams.set("sort", params.sort);
154
+ if (params.order) searchParams.set("order", params.order);
155
+ return searchParams.toString();
156
+ }
157
+ function useServices(params) {
141
158
  const config = usePlatformConfig();
159
+ const queryString = (0, import_react3.useMemo)(() => buildQuery(params), [
160
+ params?.category,
161
+ params?.staffId,
162
+ params?.isActive,
163
+ params?.minPrice,
164
+ params?.maxPrice,
165
+ params?.search,
166
+ params?.ids,
167
+ params?.limit,
168
+ params?.sort,
169
+ params?.order
170
+ ]);
171
+ const enabled = params?.enabled ?? true;
142
172
  return (0, import_react_query3.useQuery)({
143
- queryKey: ["idk", "services"],
144
- queryFn: () => apiRequest(config, "/services")
173
+ queryKey: ["idk", "services", queryString],
174
+ queryFn: () => apiRequest(
175
+ config,
176
+ queryString ? `/services?${queryString}` : "/services"
177
+ ),
178
+ enabled
145
179
  });
146
180
  }
147
181
 
148
182
  // src/core/hooks/useTeam.ts
183
+ var import_react4 = require("react");
149
184
  var import_react_query4 = require("@tanstack/react-query");
150
- function useTeam() {
185
+ function buildQuery2(params) {
186
+ if (!params) return "";
187
+ const searchParams = new URLSearchParams();
188
+ const roles = Array.isArray(params.role) ? params.role : params.role ? [params.role] : [];
189
+ if (roles.length) searchParams.set("role", roles.join(","));
190
+ if (typeof params.isActive === "boolean") searchParams.set("isActive", String(params.isActive));
191
+ if (typeof params.acceptsNewBookings === "boolean") {
192
+ searchParams.set("acceptsNewBookings", String(params.acceptsNewBookings));
193
+ }
194
+ if (params.search) searchParams.set("search", params.search);
195
+ if (params.ids?.length) searchParams.set("ids", params.ids.join(","));
196
+ if (params.serviceId) searchParams.set("serviceId", params.serviceId);
197
+ if (typeof params.limit === "number") searchParams.set("limit", String(params.limit));
198
+ if (params.sort) searchParams.set("sort", params.sort);
199
+ if (params.order) searchParams.set("order", params.order);
200
+ return searchParams.toString();
201
+ }
202
+ function useTeam(params) {
151
203
  const config = usePlatformConfig();
204
+ const queryString = (0, import_react4.useMemo)(() => buildQuery2(params), [
205
+ params?.role,
206
+ params?.isActive,
207
+ params?.acceptsNewBookings,
208
+ params?.search,
209
+ params?.ids,
210
+ params?.serviceId,
211
+ params?.limit,
212
+ params?.sort,
213
+ params?.order
214
+ ]);
215
+ const enabled = params?.enabled ?? true;
152
216
  return (0, import_react_query4.useQuery)({
153
- queryKey: ["idk", "team"],
154
- queryFn: () => apiRequest(config, "/team")
217
+ queryKey: ["idk", "team", queryString],
218
+ queryFn: () => apiRequest(
219
+ config,
220
+ queryString ? `/team?${queryString}` : "/team"
221
+ ),
222
+ enabled
155
223
  });
156
224
  }
157
225
 
158
226
  // src/core/hooks/useAvailability.ts
159
- var import_react3 = require("react");
227
+ var import_react5 = require("react");
160
228
  var import_react_query5 = require("@tanstack/react-query");
161
- function buildQuery(params) {
229
+ function buildQuery3(params) {
162
230
  const searchParams = new URLSearchParams();
163
231
  searchParams.set("serviceId", params.serviceId);
164
232
  if (params.staffId) searchParams.set("staffId", params.staffId);
@@ -170,8 +238,8 @@ function buildQuery(params) {
170
238
  }
171
239
  function useAvailability(params) {
172
240
  const config = usePlatformConfig();
173
- const queryString = (0, import_react3.useMemo)(
174
- () => buildQuery(params),
241
+ const queryString = (0, import_react5.useMemo)(
242
+ () => buildQuery3(params),
175
243
  [params.serviceId, params.staffId, params.startDate, params.endDate, params.date, params.days]
176
244
  );
177
245
  const enabled = params.enabled ?? Boolean(params.serviceId);
@@ -221,6 +289,7 @@ function ServiceCard({
221
289
  className,
222
290
  showDescription = true,
223
291
  showPrice = true,
292
+ showImage = false,
224
293
  onSelect
225
294
  }) {
226
295
  const handleClick = () => {
@@ -234,6 +303,7 @@ function ServiceCard({
234
303
  role: onSelect ? "button" : void 0,
235
304
  tabIndex: onSelect ? 0 : void 0,
236
305
  children: [
306
+ showImage && service.image?.url ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "idk-card__media", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: service.image.url, alt: service.name }) }) : null,
237
307
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "idk-card__header", children: [
238
308
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "idk-card__title", children: service.name }),
239
309
  showPrice && typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "idk-card__price", children: [
@@ -262,14 +332,31 @@ function ServicesList({
262
332
  className,
263
333
  showDescription,
264
334
  showPrice,
335
+ showImage,
336
+ query,
337
+ filter,
338
+ sort,
339
+ limit,
340
+ emptyMessage = "No services available.",
341
+ loadingMessage = "Loading services...",
265
342
  onSelect
266
343
  }) {
267
- const { data, isLoading, error } = useServices();
344
+ const { data, isLoading, error } = useServices(query);
268
345
  if (isLoading) {
269
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "idk-state", children: "Loading services..." });
346
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "idk-state", children: loadingMessage });
270
347
  }
271
- if (error || !data?.services?.length) {
272
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "idk-state", children: "No services available." });
348
+ let services = data?.services ? [...data.services] : [];
349
+ if (filter) {
350
+ services = services.filter(filter);
351
+ }
352
+ if (sort) {
353
+ services = services.sort(sort);
354
+ }
355
+ if (typeof limit === "number") {
356
+ services = services.slice(0, Math.max(0, limit));
357
+ }
358
+ if (error || services.length === 0) {
359
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "idk-state", children: emptyMessage });
273
360
  }
274
361
  const gridStyle = layout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` } : void 0;
275
362
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -281,12 +368,13 @@ function ServicesList({
281
368
  className
282
369
  ].filter(Boolean).join(" "),
283
370
  style: gridStyle,
284
- children: data.services.map((service) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
371
+ children: services.map((service) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
285
372
  ServiceCard,
286
373
  {
287
374
  service,
288
375
  showDescription,
289
376
  showPrice,
377
+ showImage,
290
378
  onSelect
291
379
  },
292
380
  service.id
@@ -295,52 +383,145 @@ function ServicesList({
295
383
  );
296
384
  }
297
385
 
298
- // src/components/TeamMember.tsx
386
+ // src/lib/linkify.tsx
299
387
  var import_jsx_runtime4 = require("react/jsx-runtime");
388
+ var LINK_REGEX = /((https?:\/\/|www\.)[^\s]+|[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}|(?:[a-z0-9-]+\.)+[a-z]{2,}(?:\/[^\s]*)?)/gi;
389
+ function buildHref(raw) {
390
+ if (raw.includes("@") && !raw.startsWith("http")) {
391
+ return `mailto:${raw}`;
392
+ }
393
+ if (raw.startsWith("http://") || raw.startsWith("https://")) {
394
+ return raw;
395
+ }
396
+ if (raw.startsWith("www.")) {
397
+ return `https://${raw}`;
398
+ }
399
+ return `https://${raw}`;
400
+ }
401
+ function trimTrailingPunctuation(value) {
402
+ let text = value;
403
+ let trailing = "";
404
+ while (text && /[),.!?:;]+$/.test(text)) {
405
+ trailing = text.slice(-1) + trailing;
406
+ text = text.slice(0, -1);
407
+ }
408
+ return { text, trailing };
409
+ }
410
+ function renderLinkedText(text) {
411
+ if (!text) return null;
412
+ const matches = Array.from(text.matchAll(LINK_REGEX));
413
+ if (matches.length === 0) return text;
414
+ const nodes = [];
415
+ let lastIndex = 0;
416
+ matches.forEach((match, index) => {
417
+ const matchText = match[0];
418
+ const start = match.index ?? 0;
419
+ if (start > lastIndex) {
420
+ nodes.push(text.slice(lastIndex, start));
421
+ }
422
+ const { text: cleanText, trailing } = trimTrailingPunctuation(matchText);
423
+ if (cleanText) {
424
+ const href = buildHref(cleanText);
425
+ nodes.push(
426
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", children: cleanText }, `link-${start}-${index}`)
427
+ );
428
+ }
429
+ if (trailing) {
430
+ nodes.push(trailing);
431
+ }
432
+ lastIndex = start + matchText.length;
433
+ });
434
+ if (lastIndex < text.length) {
435
+ nodes.push(text.slice(lastIndex));
436
+ }
437
+ return nodes;
438
+ }
439
+
440
+ // src/components/TeamMember.tsx
441
+ var import_jsx_runtime5 = require("react/jsx-runtime");
300
442
  function TeamMember({
301
443
  member,
302
444
  className,
303
445
  showRole = true,
304
- showEmail = false
446
+ showEmail = false,
447
+ showBio = false,
448
+ layout = "card",
449
+ imagePosition = "left"
305
450
  }) {
306
451
  const initials = member.name?.split(" ").map((part) => part[0]).slice(0, 2).join("").toUpperCase();
307
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: ["idk-card", className].filter(Boolean).join(" "), children: [
308
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "idk-team__avatar", children: member.photo?.url ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: member.photo.url, alt: member.name }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: initials }) }),
309
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "idk-team__info", children: [
310
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "idk-card__title", children: member.name }),
311
- showRole ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
312
- showEmail ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-card__meta", children: member.email }) : null
452
+ const avatar = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-team__avatar", children: member.photo?.url ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: member.photo.url, alt: member.name }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: initials }) });
453
+ if (layout === "profile") {
454
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: ["idk-team__profile", className].filter(Boolean).join(" "), children: [
455
+ imagePosition === "left" ? avatar : null,
456
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "idk-team__info", children: [
457
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "idk-card__title", children: member.name }),
458
+ showRole ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
459
+ showBio && member.bio ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "idk-team__bio", children: renderLinkedText(member.bio) }) : null,
460
+ showEmail ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "idk-card__meta", children: member.email }) : null
461
+ ] }),
462
+ imagePosition === "right" ? avatar : null
463
+ ] });
464
+ }
465
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: ["idk-card", className].filter(Boolean).join(" "), children: [
466
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-team__avatar", children: member.photo?.url ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: member.photo.url, alt: member.name }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: initials }) }),
467
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "idk-team__info", children: [
468
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "idk-card__title", children: member.name }),
469
+ showRole ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
470
+ showBio && member.bio ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "idk-team__bio", children: renderLinkedText(member.bio) }) : null,
471
+ showEmail ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "idk-card__meta", children: member.email }) : null
313
472
  ] })
314
473
  ] });
315
474
  }
316
475
 
317
476
  // src/components/TeamGrid.tsx
318
- var import_jsx_runtime5 = require("react/jsx-runtime");
477
+ var import_jsx_runtime6 = require("react/jsx-runtime");
319
478
  function TeamGrid({
320
479
  columns = 3,
321
480
  className,
322
481
  showRole,
323
- showEmail
482
+ showEmail,
483
+ showBio,
484
+ layout = "card",
485
+ imagePosition = "left",
486
+ query,
487
+ filter,
488
+ sort,
489
+ limit,
490
+ emptyMessage = "No team members available.",
491
+ loadingMessage = "Loading team..."
324
492
  }) {
325
- const { data, isLoading, error } = useTeam();
493
+ const { data, isLoading, error } = useTeam(query);
326
494
  if (isLoading) {
327
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-state", children: "Loading team..." });
495
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "idk-state", children: loadingMessage });
496
+ }
497
+ let members = data?.team ? [...data.team] : [];
498
+ if (filter) {
499
+ members = members.filter(filter);
500
+ }
501
+ if (sort) {
502
+ members = members.sort(sort);
503
+ }
504
+ if (typeof limit === "number") {
505
+ members = members.slice(0, Math.max(0, limit));
328
506
  }
329
- if (error || !data?.team?.length) {
330
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-state", children: "No team members available." });
507
+ if (error || members.length === 0) {
508
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "idk-state", children: emptyMessage });
331
509
  }
332
510
  const gridStyle = { gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` };
333
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
511
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
334
512
  "div",
335
513
  {
336
514
  className: ["idk-team", className].filter(Boolean).join(" "),
337
515
  style: gridStyle,
338
- children: data.team.map((member) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
516
+ children: members.map((member) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
339
517
  TeamMember,
340
518
  {
341
519
  member,
342
520
  showRole,
343
- showEmail
521
+ showEmail,
522
+ showBio,
523
+ layout,
524
+ imagePosition
344
525
  },
345
526
  member.id
346
527
  ))
@@ -349,9 +530,9 @@ function TeamGrid({
349
530
  }
350
531
 
351
532
  // src/components/ContactForm.tsx
352
- var import_react4 = require("react");
533
+ var import_react6 = require("react");
353
534
  var import_react_query7 = require("@tanstack/react-query");
354
- var import_jsx_runtime6 = require("react/jsx-runtime");
535
+ var import_jsx_runtime7 = require("react/jsx-runtime");
355
536
  var DEFAULT_FIELDS = ["name", "email", "phone", "message"];
356
537
  function ContactForm({
357
538
  fields = DEFAULT_FIELDS,
@@ -362,7 +543,7 @@ function ContactForm({
362
543
  onError
363
544
  }) {
364
545
  const config = usePlatformConfig();
365
- const [formState, setFormState] = (0, import_react4.useState)({
546
+ const [formState, setFormState] = (0, import_react6.useState)({
366
547
  name: "",
367
548
  email: "",
368
549
  phone: "",
@@ -397,10 +578,10 @@ function ContactForm({
397
578
  formType
398
579
  });
399
580
  };
400
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { className: ["idk-form", className].filter(Boolean).join(" "), onSubmit: handleSubmit, children: [
401
- fields.includes("name") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "idk-form__field", children: [
402
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Name" }),
403
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
581
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { className: ["idk-form", className].filter(Boolean).join(" "), onSubmit: handleSubmit, children: [
582
+ fields.includes("name") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
583
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Name" }),
584
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
404
585
  "input",
405
586
  {
406
587
  value: formState.name,
@@ -409,9 +590,9 @@ function ContactForm({
409
590
  }
410
591
  )
411
592
  ] }),
412
- fields.includes("email") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "idk-form__field", children: [
413
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Email" }),
414
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
593
+ fields.includes("email") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Email" }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
415
596
  "input",
416
597
  {
417
598
  type: "email",
@@ -421,9 +602,9 @@ function ContactForm({
421
602
  }
422
603
  )
423
604
  ] }),
424
- fields.includes("phone") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "idk-form__field", children: [
425
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Phone" }),
426
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
605
+ fields.includes("phone") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
606
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Phone" }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
427
608
  "input",
428
609
  {
429
610
  value: formState.phone,
@@ -431,9 +612,9 @@ function ContactForm({
431
612
  }
432
613
  )
433
614
  ] }),
434
- fields.includes("subject") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "idk-form__field", children: [
435
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Subject" }),
436
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
615
+ fields.includes("subject") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
616
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Subject" }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
437
618
  "input",
438
619
  {
439
620
  value: formState.subject,
@@ -441,9 +622,9 @@ function ContactForm({
441
622
  }
442
623
  )
443
624
  ] }),
444
- fields.includes("message") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "idk-form__field", children: [
445
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Message" }),
446
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
625
+ fields.includes("message") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
626
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Message" }),
627
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
447
628
  "textarea",
448
629
  {
449
630
  value: formState.message,
@@ -453,227 +634,942 @@ function ContactForm({
453
634
  }
454
635
  )
455
636
  ] }),
456
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "submit", className: "idk-button", disabled: mutation.isPending, children: mutation.isPending ? "Sending..." : submitLabel }),
457
- mutation.isError ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "idk-form__error", children: "Something went wrong. Please try again." }) : null,
458
- mutation.isSuccess ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "idk-form__success", children: "Thanks! We'll be in touch soon." }) : null
637
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "submit", className: "idk-button", disabled: mutation.isPending, children: mutation.isPending ? "Sending..." : submitLabel }),
638
+ mutation.isError ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "idk-form__error", children: "Something went wrong. Please try again." }) : null,
639
+ mutation.isSuccess ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "idk-form__success", children: "Thanks! We'll be in touch soon." }) : null
459
640
  ] });
460
641
  }
461
642
 
462
643
  // src/components/BookingWidget.tsx
463
- var import_react5 = require("react");
464
- var import_jsx_runtime7 = require("react/jsx-runtime");
644
+ var import_react7 = require("react");
645
+ var import_jsx_runtime8 = require("react/jsx-runtime");
646
+ var parseDateOnly = (value) => {
647
+ if (!value) return null;
648
+ const normalized = value.includes("T") ? value.split("T")[0] : value;
649
+ const parsed = /* @__PURE__ */ new Date(`${normalized}T00:00:00`);
650
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
651
+ };
652
+ var addDays = (dateStr, days) => {
653
+ const parsed = parseDateOnly(dateStr);
654
+ if (!parsed) return dateStr;
655
+ parsed.setDate(parsed.getDate() + days);
656
+ return parsed.toISOString().slice(0, 10);
657
+ };
465
658
  function BookingWidget({
466
659
  className,
467
660
  showStaffSelection = true,
468
- onSuccess
661
+ onSuccess,
662
+ services,
663
+ servicesQuery,
664
+ teamQuery,
665
+ serviceFilter,
666
+ teamFilter,
667
+ title = "Book an Appointment",
668
+ subtitle,
669
+ autoAdvanceOnSelect = false,
670
+ scrollToStep = true,
671
+ scrollOffset = 96,
672
+ enableServiceSearch = true,
673
+ serviceSearchPlaceholder = "Search services",
674
+ enableCategoryFilter = false,
675
+ categoryLabel = "All categories",
676
+ serviceListMaxHeight,
677
+ serviceLayout = "list",
678
+ serviceColumns = 2,
679
+ servicePageSize = 10,
680
+ showServicePagination,
681
+ staffLayout = "grid",
682
+ staffColumns = 2,
683
+ showAnyStaffOption = true,
684
+ staffPageSize = 8,
685
+ showStaffPagination,
686
+ timeLayout = "split",
687
+ availabilityDays: availabilityDaysProp = 7,
688
+ enableDatePaging = true,
689
+ datePickerMode = "list",
690
+ calendarVariant = "default",
691
+ maxAdvanceMonths = 3,
692
+ disabledWeekdays = [],
693
+ showFullyBookedLabel = true
469
694
  }) {
470
- const { data: servicesData, isLoading: servicesLoading } = useServices();
471
- const { data: teamData } = useTeam();
695
+ const resolvedServicesQuery = (0, import_react7.useMemo)(
696
+ () => services ? { ...servicesQuery, enabled: false } : servicesQuery,
697
+ [services, servicesQuery]
698
+ );
699
+ const { data: servicesData, isLoading: servicesLoading } = useServices(resolvedServicesQuery);
700
+ const { data: teamData } = useTeam(teamQuery);
472
701
  const createBooking = useCreateBooking();
473
- const [step, setStep] = (0, import_react5.useState)("service");
474
- const [selectedService, setSelectedService] = (0, import_react5.useState)(null);
475
- const [selectedStaff, setSelectedStaff] = (0, import_react5.useState)(null);
476
- const [selectedDate, setSelectedDate] = (0, import_react5.useState)(null);
477
- const [selectedTime, setSelectedTime] = (0, import_react5.useState)(null);
478
- const [selectedEndTime, setSelectedEndTime] = (0, import_react5.useState)(null);
479
- const [details, setDetails] = (0, import_react5.useState)({
702
+ const widgetRef = (0, import_react7.useRef)(null);
703
+ const [step, setStep] = (0, import_react7.useState)("service");
704
+ const [selectedService, setSelectedService] = (0, import_react7.useState)(null);
705
+ const [selectedStaff, setSelectedStaff] = (0, import_react7.useState)(null);
706
+ const [selectedDate, setSelectedDate] = (0, import_react7.useState)(null);
707
+ const [selectedTime, setSelectedTime] = (0, import_react7.useState)(null);
708
+ const [selectedEndTime, setSelectedEndTime] = (0, import_react7.useState)(null);
709
+ const [serviceSearch, setServiceSearch] = (0, import_react7.useState)("");
710
+ const [categoryFilter, setCategoryFilter] = (0, import_react7.useState)("all");
711
+ const [servicePage, setServicePage] = (0, import_react7.useState)(1);
712
+ const [staffPage, setStaffPage] = (0, import_react7.useState)(1);
713
+ const [availabilityDays, setAvailabilityDays] = (0, import_react7.useState)(
714
+ Math.max(1, availabilityDaysProp)
715
+ );
716
+ const [availabilityStartDate, setAvailabilityStartDate] = (0, import_react7.useState)(
717
+ () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
718
+ );
719
+ const [calendarMonth, setCalendarMonth] = (0, import_react7.useState)(() => {
720
+ const now = /* @__PURE__ */ new Date();
721
+ return new Date(now.getFullYear(), now.getMonth(), 1);
722
+ });
723
+ const [bookingError, setBookingError] = (0, import_react7.useState)(null);
724
+ const [details, setDetails] = (0, import_react7.useState)({
480
725
  name: "",
481
726
  email: "",
482
727
  phone: "",
483
728
  notes: ""
484
729
  });
485
- const staffOptions = (0, import_react5.useMemo)(() => {
730
+ const servicesList = (0, import_react7.useMemo)(() => {
731
+ const list = services ? [...services] : servicesData?.services ? [...servicesData.services] : [];
732
+ return serviceFilter ? list.filter(serviceFilter) : list;
733
+ }, [services, servicesData, serviceFilter]);
734
+ const categories = (0, import_react7.useMemo)(() => {
735
+ const set = /* @__PURE__ */ new Set();
736
+ servicesList.forEach((service) => {
737
+ if (service.category) {
738
+ set.add(service.category);
739
+ }
740
+ });
741
+ return Array.from(set).sort();
742
+ }, [servicesList]);
743
+ const filteredServices = (0, import_react7.useMemo)(() => {
744
+ const search = serviceSearch.trim().toLowerCase();
745
+ return servicesList.filter((service) => {
746
+ const matchesSearch = !search || service.name.toLowerCase().includes(search) || (service.description || "").toLowerCase().includes(search) || (service.category || "").toLowerCase().includes(search);
747
+ const matchesCategory = categoryFilter === "all" ? true : service.category === categoryFilter;
748
+ return matchesSearch && matchesCategory;
749
+ });
750
+ }, [servicesList, serviceSearch, categoryFilter]);
751
+ (0, import_react7.useEffect)(() => {
752
+ setServicePage(1);
753
+ }, [serviceSearch, categoryFilter]);
754
+ (0, import_react7.useEffect)(() => {
755
+ setStaffPage(1);
756
+ }, [selectedService?.id, teamData, teamFilter]);
757
+ const pagedServices = (0, import_react7.useMemo)(() => {
758
+ const pageSize = Math.max(1, servicePageSize);
759
+ return filteredServices.slice(0, pageSize * servicePage);
760
+ }, [filteredServices, servicePage, servicePageSize]);
761
+ const hasMoreServices = filteredServices.length > pagedServices.length;
762
+ const shouldPaginate = showServicePagination ?? hasMoreServices;
763
+ const staffOptions = (0, import_react7.useMemo)(() => {
486
764
  if (!selectedService) return [];
487
765
  if (selectedService.assignedStaff && selectedService.assignedStaff.length > 0) {
488
- return selectedService.assignedStaff;
766
+ const assigned = selectedService.assignedStaff;
767
+ return teamFilter ? assigned.filter(teamFilter) : assigned;
489
768
  }
490
- return teamData?.team || [];
491
- }, [selectedService, teamData]);
769
+ const teamList = teamData?.team || [];
770
+ return teamFilter ? teamList.filter(teamFilter) : teamList;
771
+ }, [selectedService, teamData, teamFilter]);
772
+ const pagedStaff = (0, import_react7.useMemo)(() => {
773
+ const pageSize = Math.max(1, staffPageSize);
774
+ return staffOptions.slice(0, pageSize * staffPage);
775
+ }, [staffOptions, staffPage, staffPageSize]);
776
+ const hasMoreStaff = staffOptions.length > pagedStaff.length;
777
+ const shouldPaginateStaff = showStaffPagination ?? hasMoreStaff;
778
+ const availabilityEndDate = (0, import_react7.useMemo)(
779
+ () => addDays(availabilityStartDate, Math.max(1, availabilityDays) - 1),
780
+ [availabilityStartDate, availabilityDays]
781
+ );
492
782
  const availability = useAvailability({
493
783
  serviceId: selectedService?.id || "",
494
784
  staffId: selectedStaff?.id,
495
- days: 7,
785
+ startDate: availabilityStartDate,
786
+ endDate: availabilityEndDate,
496
787
  enabled: Boolean(selectedService)
497
788
  });
789
+ const timeZone = availability.data?.timeZone || "America/Chicago";
790
+ const safeTimeZone = (0, import_react7.useMemo)(() => {
791
+ if (!timeZone) return void 0;
792
+ try {
793
+ new Intl.DateTimeFormat("en-US", { timeZone }).format(/* @__PURE__ */ new Date());
794
+ return timeZone;
795
+ } catch {
796
+ return void 0;
797
+ }
798
+ }, [timeZone]);
799
+ const timeZoneLabel = (0, import_react7.useMemo)(() => {
800
+ if (timeZone) return timeZone;
801
+ try {
802
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
803
+ } catch {
804
+ return "Local time";
805
+ }
806
+ }, [timeZone]);
807
+ const dateLabelFormatter = (0, import_react7.useMemo)(
808
+ () => new Intl.DateTimeFormat("en-US", {
809
+ weekday: "short",
810
+ month: "short",
811
+ day: "numeric"
812
+ }),
813
+ []
814
+ );
815
+ const dateHeadingFormatter = (0, import_react7.useMemo)(
816
+ () => new Intl.DateTimeFormat("en-US", {
817
+ weekday: "long",
818
+ month: "long",
819
+ day: "numeric"
820
+ }),
821
+ []
822
+ );
823
+ const timeFormatter = (0, import_react7.useMemo)(
824
+ () => new Intl.DateTimeFormat("en-US", {
825
+ timeZone: safeTimeZone,
826
+ hour: "numeric",
827
+ minute: "2-digit"
828
+ }),
829
+ [safeTimeZone]
830
+ );
498
831
  const requiresStaff = showStaffSelection || selectedService?.requiresStaffSelection || selectedService?.schedulingType === "customer-choice";
832
+ const canSkipStaff = !selectedService?.requiresStaffSelection;
833
+ const totalSteps = requiresStaff ? 4 : 3;
834
+ const stepNumber = (() => {
835
+ if (step === "service") return 1;
836
+ if (step === "staff") return 2;
837
+ if (step === "time") return requiresStaff ? 3 : 2;
838
+ if (step === "details") return requiresStaff ? 4 : 3;
839
+ return totalSteps;
840
+ })();
841
+ const scrollToTop = (0, import_react7.useCallback)(() => {
842
+ if (!scrollToStep || !widgetRef.current) return;
843
+ const top = widgetRef.current.getBoundingClientRect().top + window.scrollY;
844
+ window.scrollTo({
845
+ top: Math.max(0, top - scrollOffset),
846
+ behavior: "smooth"
847
+ });
848
+ }, [scrollToStep, scrollOffset]);
849
+ const resetSelections = () => {
850
+ setSelectedStaff(null);
851
+ setSelectedDate(null);
852
+ setSelectedTime(null);
853
+ setSelectedEndTime(null);
854
+ setBookingError(null);
855
+ };
856
+ const parseDateTime = (value, fallbackDate) => {
857
+ if (!value) return null;
858
+ const direct = new Date(value);
859
+ if (!Number.isNaN(direct.getTime())) return direct;
860
+ if (fallbackDate) {
861
+ const normalized = fallbackDate.includes("T") ? fallbackDate.split("T")[0] : fallbackDate;
862
+ const combined = /* @__PURE__ */ new Date(`${normalized}T${value}`);
863
+ if (!Number.isNaN(combined.getTime())) return combined;
864
+ }
865
+ return null;
866
+ };
867
+ const formatDateLabel = (date) => {
868
+ const parsed = parseDateOnly(date);
869
+ if (!parsed) return date;
870
+ try {
871
+ return dateLabelFormatter.format(parsed);
872
+ } catch {
873
+ return date;
874
+ }
875
+ };
876
+ const formatDateHeading = (date) => {
877
+ const parsed = parseDateOnly(date);
878
+ if (!parsed) return date;
879
+ try {
880
+ return dateHeadingFormatter.format(parsed);
881
+ } catch {
882
+ return date;
883
+ }
884
+ };
885
+ const formatTimeLabel = (iso) => {
886
+ const parsed = parseDateTime(iso, selectedDate);
887
+ if (!parsed) return iso;
888
+ try {
889
+ return timeFormatter.format(parsed);
890
+ } catch {
891
+ return parsed.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
892
+ }
893
+ };
894
+ const formatDuration = (minutes) => {
895
+ if (!Number.isFinite(minutes)) return "";
896
+ if (minutes < 60) return `${minutes} min`;
897
+ const hours = Math.floor(minutes / 60);
898
+ const remaining = minutes % 60;
899
+ return remaining === 0 ? `${hours} hr${hours > 1 ? "s" : ""}` : `${hours} hr ${remaining} min`;
900
+ };
901
+ const todayString = (0, import_react7.useMemo)(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), []);
902
+ const todayDate = (0, import_react7.useMemo)(() => {
903
+ const now = /* @__PURE__ */ new Date();
904
+ now.setHours(0, 0, 0, 0);
905
+ return now;
906
+ }, []);
907
+ const disabledWeekdaysSet = (0, import_react7.useMemo)(() => new Set(disabledWeekdays), [disabledWeekdays]);
908
+ const getMonthStart = (0, import_react7.useCallback)((date) => {
909
+ return new Date(date.getFullYear(), date.getMonth(), 1);
910
+ }, []);
911
+ const getMonthEnd = (0, import_react7.useCallback)((date) => {
912
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0);
913
+ }, []);
914
+ const getMonthDays = (0, import_react7.useCallback)(
915
+ (date) => {
916
+ const start = getMonthStart(date);
917
+ const end = getMonthEnd(date);
918
+ const days = [];
919
+ const startDay = start.getDay();
920
+ for (let i = startDay; i > 0; i -= 1) {
921
+ const d = new Date(start);
922
+ d.setDate(start.getDate() - i);
923
+ days.push({ date: d.toISOString().slice(0, 10), isCurrentMonth: false });
924
+ }
925
+ for (let day = 1; day <= end.getDate(); day += 1) {
926
+ const d = new Date(date.getFullYear(), date.getMonth(), day);
927
+ days.push({ date: d.toISOString().slice(0, 10), isCurrentMonth: true });
928
+ }
929
+ const remainder = 7 - (days.length % 7 || 7);
930
+ for (let i = 1; i <= remainder; i += 1) {
931
+ const d = new Date(end);
932
+ d.setDate(end.getDate() + i);
933
+ days.push({ date: d.toISOString().slice(0, 10), isCurrentMonth: false });
934
+ }
935
+ return days;
936
+ },
937
+ [getMonthEnd, getMonthStart]
938
+ );
939
+ const maxAdvanceDate = (0, import_react7.useMemo)(() => {
940
+ if (!maxAdvanceMonths || maxAdvanceMonths <= 0) return null;
941
+ const maxMonth = new Date(todayDate.getFullYear(), todayDate.getMonth() + maxAdvanceMonths - 1, 1);
942
+ return getMonthEnd(maxMonth);
943
+ }, [getMonthEnd, maxAdvanceMonths, todayDate]);
944
+ const maxAdvanceMonthStart = (0, import_react7.useMemo)(() => {
945
+ if (!maxAdvanceMonths || maxAdvanceMonths <= 0) return null;
946
+ return new Date(todayDate.getFullYear(), todayDate.getMonth() + maxAdvanceMonths - 1, 1);
947
+ }, [maxAdvanceMonths, todayDate]);
948
+ const handlePrevDates = () => {
949
+ if (availabilityStartDate <= todayString) return;
950
+ setAvailabilityStartDate((prev) => addDays(prev, -availabilityDays));
951
+ setSelectedDate(null);
952
+ setSelectedTime(null);
953
+ setSelectedEndTime(null);
954
+ setBookingError(null);
955
+ };
956
+ const handleNextDates = () => {
957
+ if (maxAdvanceDate) {
958
+ const nextStart = parseDateOnly(addDays(availabilityStartDate, availabilityDays));
959
+ if (nextStart && nextStart > maxAdvanceDate) {
960
+ return;
961
+ }
962
+ }
963
+ setAvailabilityStartDate((prev) => addDays(prev, availabilityDays));
964
+ setSelectedDate(null);
965
+ setSelectedTime(null);
966
+ setSelectedEndTime(null);
967
+ setBookingError(null);
968
+ };
969
+ const handlePrevMonth = () => {
970
+ setCalendarMonth((prev) => new Date(prev.getFullYear(), prev.getMonth() - 1, 1));
971
+ setSelectedDate(null);
972
+ setSelectedTime(null);
973
+ setSelectedEndTime(null);
974
+ setBookingError(null);
975
+ };
976
+ const handleNextMonth = () => {
977
+ if (maxAdvanceMonthStart) {
978
+ const nextMonth = new Date(calendarMonth.getFullYear(), calendarMonth.getMonth() + 1, 1);
979
+ if (nextMonth > maxAdvanceMonthStart) return;
980
+ }
981
+ setCalendarMonth((prev) => new Date(prev.getFullYear(), prev.getMonth() + 1, 1));
982
+ setSelectedDate(null);
983
+ setSelectedTime(null);
984
+ setSelectedEndTime(null);
985
+ setBookingError(null);
986
+ };
987
+ (0, import_react7.useEffect)(() => {
988
+ if (step !== "time") return;
989
+ if (!availability.data?.dates?.length) return;
990
+ const matches = selectedDate ? availability.data.dates.some((entry) => entry.date === selectedDate) : false;
991
+ if (matches) return;
992
+ setSelectedDate(availability.data.dates[0].date);
993
+ }, [step, availability.data, selectedDate]);
994
+ (0, import_react7.useEffect)(() => {
995
+ if (!selectedService) return;
996
+ if (datePickerMode === "calendar") {
997
+ const now = /* @__PURE__ */ new Date();
998
+ setCalendarMonth(new Date(now.getFullYear(), now.getMonth(), 1));
999
+ return;
1000
+ }
1001
+ setAvailabilityStartDate((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
1002
+ setAvailabilityDays(Math.max(1, availabilityDaysProp));
1003
+ }, [selectedService?.id, selectedStaff?.id, availabilityDaysProp, datePickerMode]);
1004
+ (0, import_react7.useEffect)(() => {
1005
+ if (datePickerMode !== "calendar") return;
1006
+ const start = getMonthStart(calendarMonth);
1007
+ const end = getMonthEnd(calendarMonth);
1008
+ const startString = start.toISOString().slice(0, 10);
1009
+ const daysInMonth = end.getDate();
1010
+ setAvailabilityStartDate(startString);
1011
+ setAvailabilityDays(daysInMonth);
1012
+ }, [calendarMonth, datePickerMode, getMonthEnd, getMonthStart]);
1013
+ const handleBack = () => {
1014
+ if (step === "service") return;
1015
+ if (step === "staff") {
1016
+ setStep("service");
1017
+ } else if (step === "time") {
1018
+ if (requiresStaff) {
1019
+ setStep("staff");
1020
+ } else {
1021
+ setStep("service");
1022
+ }
1023
+ } else if (step === "details") {
1024
+ setStep("time");
1025
+ } else if (step === "done") {
1026
+ setStep("details");
1027
+ }
1028
+ setBookingError(null);
1029
+ scrollToTop();
1030
+ };
1031
+ const handleServiceContinue = () => {
1032
+ if (!selectedService) return;
1033
+ if (requiresStaff) {
1034
+ setStep("staff");
1035
+ } else {
1036
+ setStep("time");
1037
+ }
1038
+ scrollToTop();
1039
+ };
1040
+ const handleStaffContinue = () => {
1041
+ if (!canSkipStaff && !selectedStaff) return;
1042
+ setStep("time");
1043
+ scrollToTop();
1044
+ };
1045
+ const handleTimeContinue = () => {
1046
+ if (!selectedTime || !selectedEndTime) return;
1047
+ setStep("details");
1048
+ scrollToTop();
1049
+ };
499
1050
  const handleSubmit = async (event) => {
500
1051
  event.preventDefault();
501
1052
  if (!selectedService || !selectedDate || !selectedTime || !selectedEndTime) return;
502
- const start = /* @__PURE__ */ new Date(`${selectedDate}T${selectedTime}:00`);
503
- const end = /* @__PURE__ */ new Date(`${selectedDate}T${selectedEndTime}:00`);
504
- const result = await createBooking.mutateAsync({
505
- serviceId: selectedService.id,
506
- staffId: selectedStaff?.id,
507
- startTime: start.toISOString(),
508
- endTime: end.toISOString(),
509
- customerName: details.name,
510
- customerEmail: details.email,
511
- customerPhone: details.phone || void 0,
512
- customerNotes: details.notes || void 0
513
- });
514
- setStep("done");
515
- onSuccess?.(result?.booking);
1053
+ const start = parseDateTime(selectedTime, selectedDate);
1054
+ const end = parseDateTime(selectedEndTime, selectedDate);
1055
+ if (!start || !end) {
1056
+ setBookingError("Please select a valid time slot.");
1057
+ return;
1058
+ }
1059
+ try {
1060
+ setBookingError(null);
1061
+ const result = await createBooking.mutateAsync({
1062
+ serviceId: selectedService.id,
1063
+ staffId: selectedStaff?.id,
1064
+ startTime: start.toISOString(),
1065
+ endTime: end.toISOString(),
1066
+ customerName: details.name,
1067
+ customerEmail: details.email,
1068
+ customerPhone: details.phone || void 0,
1069
+ customerNotes: details.notes || void 0
1070
+ });
1071
+ setStep("done");
1072
+ onSuccess?.(result?.booking);
1073
+ scrollToTop();
1074
+ } catch (error) {
1075
+ setBookingError(
1076
+ error instanceof Error ? error.message : "Unable to complete booking. Please try again."
1077
+ );
1078
+ }
516
1079
  };
517
- if (servicesLoading) {
518
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "Loading booking options..." });
1080
+ const isServicesLoading = services ? false : servicesLoading;
1081
+ if (isServicesLoading) {
1082
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "Loading booking options..." });
519
1083
  }
520
- if (!servicesData?.services?.length) {
521
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "No services available." });
1084
+ if (!servicesList.length) {
1085
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "No services available." });
522
1086
  }
523
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: ["idk-booking", className].filter(Boolean).join(" "), children: [
524
- step === "service" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__step", children: [
525
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: "Select a service" }),
526
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-services idk-services--list", children: servicesData.services.map((service) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
527
- "button",
528
- {
529
- type: "button",
530
- className: "idk-card idk-card--clickable",
531
- onClick: () => {
532
- setSelectedService(service);
533
- if (requiresStaff) {
534
- setStep("staff");
535
- } else {
536
- setStep("time");
1087
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1088
+ "div",
1089
+ {
1090
+ ref: widgetRef,
1091
+ className: ["idk-booking", className].filter(Boolean).join(" "),
1092
+ children: [
1093
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__header", children: [
1094
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__title-wrap", children: [
1095
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "idk-booking__title", children: title }),
1096
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "idk-booking__subtitle", children: subtitle }) : null
1097
+ ] }),
1098
+ step !== "service" && step !== "done" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "idk-link", onClick: handleBack, children: "Back" }) : null
1099
+ ] }),
1100
+ step !== "done" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__progress", children: [
1101
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
1102
+ "Step ",
1103
+ stepNumber,
1104
+ " of ",
1105
+ totalSteps
1106
+ ] }),
1107
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-booking__bar", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1108
+ "div",
1109
+ {
1110
+ className: "idk-booking__bar-fill",
1111
+ style: { width: `${stepNumber / totalSteps * 100}%` }
537
1112
  }
538
- },
539
- children: [
540
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-card__header", children: [
541
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__title", children: service.name }),
542
- typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "idk-card__price", children: [
543
- "$",
544
- (service.price / 100).toFixed(2)
545
- ] }) : null
546
- ] }),
547
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-card__meta", children: [
548
- service.duration,
549
- " min"
1113
+ ) })
1114
+ ] }) : null,
1115
+ step === "service" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__step", children: [
1116
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: "Select a service" }),
1117
+ enableServiceSearch || enableCategoryFilter ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__filters", children: [
1118
+ enableServiceSearch ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1119
+ "input",
1120
+ {
1121
+ type: "search",
1122
+ value: serviceSearch,
1123
+ onChange: (event) => setServiceSearch(event.target.value),
1124
+ placeholder: serviceSearchPlaceholder
1125
+ }
1126
+ ) : null,
1127
+ enableCategoryFilter && categories.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1128
+ "select",
1129
+ {
1130
+ value: categoryFilter,
1131
+ onChange: (event) => setCategoryFilter(event.target.value),
1132
+ children: [
1133
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "all", children: categoryLabel }),
1134
+ categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: category, children: category }, category))
1135
+ ]
1136
+ }
1137
+ ) : null
1138
+ ] }) : null,
1139
+ filteredServices.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "No services match your filters." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1140
+ "div",
1141
+ {
1142
+ className: [
1143
+ "idk-services",
1144
+ serviceLayout === "grid" ? "idk-services--grid" : "idk-services--list"
1145
+ ].join(" "),
1146
+ style: {
1147
+ ...serviceLayout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, serviceColumns)}, minmax(0, 1fr))` } : void 0,
1148
+ ...serviceListMaxHeight ? { maxHeight: serviceListMaxHeight, overflow: "auto" } : void 0
1149
+ },
1150
+ children: pagedServices.map((service) => {
1151
+ const isSelected = selectedService?.id === service.id;
1152
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1153
+ "button",
1154
+ {
1155
+ type: "button",
1156
+ className: ["idk-card", "idk-card--clickable", isSelected ? "is-active" : ""].join(" "),
1157
+ onClick: () => {
1158
+ setSelectedService(service);
1159
+ resetSelections();
1160
+ if (autoAdvanceOnSelect) {
1161
+ if (requiresStaff) {
1162
+ setStep("staff");
1163
+ } else {
1164
+ setStep("time");
1165
+ }
1166
+ scrollToTop();
1167
+ }
1168
+ },
1169
+ children: [
1170
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-card__header", children: [
1171
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-card__title", children: service.name }),
1172
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "idk-card__aside", children: [
1173
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-card__check", children: "\u2713" }) : null,
1174
+ typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "idk-card__price", children: [
1175
+ "$",
1176
+ (service.price / 100).toFixed(2)
1177
+ ] }) : null
1178
+ ] })
1179
+ ] }),
1180
+ service.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-card__description", children: service.description }) : null,
1181
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-card__meta", children: [
1182
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: formatDuration(service.duration) }),
1183
+ service.category ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-pill", children: service.category }) : null
1184
+ ] })
1185
+ ]
1186
+ },
1187
+ service.id
1188
+ );
1189
+ })
1190
+ }
1191
+ ),
1192
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__actions", children: [
1193
+ shouldPaginate ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1194
+ "button",
1195
+ {
1196
+ type: "button",
1197
+ className: "idk-button idk-button--ghost",
1198
+ onClick: () => setServicePage((prev) => prev + 1),
1199
+ disabled: !hasMoreServices,
1200
+ children: hasMoreServices ? "Show more services" : "All services shown"
1201
+ }
1202
+ ) : null,
1203
+ selectedService && !autoAdvanceOnSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "idk-button", onClick: handleServiceContinue, children: "Continue" }) : null
1204
+ ] })
1205
+ ] }),
1206
+ step === "staff" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__step", children: [
1207
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: "Select a team member" }),
1208
+ staffOptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "No team members available." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1209
+ "div",
1210
+ {
1211
+ className: [
1212
+ "idk-team",
1213
+ staffLayout === "grid" ? "idk-team--grid" : "idk-team--list"
1214
+ ].join(" "),
1215
+ style: staffLayout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, staffColumns)}, minmax(0, 1fr))` } : void 0,
1216
+ children: [
1217
+ showAnyStaffOption && canSkipStaff ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1218
+ "button",
1219
+ {
1220
+ type: "button",
1221
+ className: ["idk-card", "idk-card--clickable", !selectedStaff ? "is-active" : ""].join(" "),
1222
+ onClick: () => {
1223
+ setSelectedStaff(null);
1224
+ setSelectedDate(null);
1225
+ setSelectedTime(null);
1226
+ setSelectedEndTime(null);
1227
+ if (autoAdvanceOnSelect) {
1228
+ setStep("time");
1229
+ scrollToTop();
1230
+ }
1231
+ },
1232
+ children: [
1233
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-card__header", children: [
1234
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-card__title", children: "Any Available" }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-card__badge", children: "Recommended" })
1236
+ ] }),
1237
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "idk-card__description", children: "We'll match you with the first available stylist." })
1238
+ ]
1239
+ },
1240
+ "any-staff"
1241
+ ) : null,
1242
+ pagedStaff.map((staff) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1243
+ "button",
1244
+ {
1245
+ type: "button",
1246
+ className: ["idk-card", "idk-card--clickable", selectedStaff?.id === staff.id ? "is-active" : ""].join(" "),
1247
+ onClick: () => {
1248
+ setSelectedStaff(staff);
1249
+ setSelectedDate(null);
1250
+ setSelectedTime(null);
1251
+ setSelectedEndTime(null);
1252
+ setBookingError(null);
1253
+ if (autoAdvanceOnSelect) {
1254
+ setStep("time");
1255
+ scrollToTop();
1256
+ }
1257
+ },
1258
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-team__card", children: [
1259
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-team__avatar", children: staff.photo?.url ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: staff.photo.url, alt: staff.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: staff.name.split(" ").map((part) => part[0]).slice(0, 2).join("").toUpperCase() }) }),
1260
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
1261
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-card__title", children: staff.name }),
1262
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-card__meta", children: staff.role || "Staff" }),
1263
+ staff.bio ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "idk-card__description", children: renderLinkedText(staff.bio) }) : null
1264
+ ] })
1265
+ ] })
1266
+ },
1267
+ staff.id
1268
+ ))
1269
+ ]
1270
+ }
1271
+ ),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__actions", children: [
1273
+ shouldPaginateStaff ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1274
+ "button",
1275
+ {
1276
+ type: "button",
1277
+ className: "idk-button idk-button--ghost",
1278
+ onClick: () => setStaffPage((prev) => hasMoreStaff ? prev + 1 : 1),
1279
+ disabled: !hasMoreStaff && staffPage === 1,
1280
+ children: hasMoreStaff ? "Show more staff" : staffPage > 1 ? "Show fewer staff" : "All staff shown"
1281
+ }
1282
+ ) : null,
1283
+ canSkipStaff ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1284
+ "button",
1285
+ {
1286
+ type: "button",
1287
+ className: "idk-link",
1288
+ onClick: () => {
1289
+ setSelectedStaff(null);
1290
+ setStep("time");
1291
+ scrollToTop();
1292
+ },
1293
+ children: "Continue without selecting"
1294
+ }
1295
+ ) : null,
1296
+ !autoAdvanceOnSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1297
+ "button",
1298
+ {
1299
+ type: "button",
1300
+ className: "idk-button",
1301
+ onClick: handleStaffContinue,
1302
+ disabled: !canSkipStaff && !selectedStaff,
1303
+ children: "Continue"
1304
+ }
1305
+ ) : null
1306
+ ] })
1307
+ ] }),
1308
+ step === "time" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__step", children: [
1309
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: "Select a time" }),
1310
+ availability.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "Loading availability..." }) : availability.data ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: ["idk-availability", timeLayout === "split" ? "idk-availability--split" : ""].join(" "), children: [
1311
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-availability__dates", children: datePickerMode === "calendar" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1312
+ "div",
1313
+ {
1314
+ className: [
1315
+ "idk-calendar",
1316
+ calendarVariant === "compact" ? "idk-calendar--compact" : ""
1317
+ ].filter(Boolean).join(" "),
1318
+ children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-calendar__header", children: [
1320
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1321
+ "button",
1322
+ {
1323
+ type: "button",
1324
+ className: "idk-button idk-button--ghost",
1325
+ onClick: handlePrevMonth,
1326
+ disabled: calendarMonth <= getMonthStart(todayDate),
1327
+ children: "Previous"
1328
+ }
1329
+ ),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-calendar__title", children: new Intl.DateTimeFormat("en-US", {
1331
+ month: "long",
1332
+ year: "numeric"
1333
+ }).format(calendarMonth) }),
1334
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1335
+ "button",
1336
+ {
1337
+ type: "button",
1338
+ className: "idk-button idk-button--ghost",
1339
+ onClick: handleNextMonth,
1340
+ disabled: Boolean(maxAdvanceMonthStart && calendarMonth >= maxAdvanceMonthStart),
1341
+ children: "Next"
1342
+ }
1343
+ )
1344
+ ] }),
1345
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-calendar__weekdays", children: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((label) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: label }, label)) }),
1346
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-calendar__grid", children: getMonthDays(calendarMonth).map((day) => {
1347
+ const entry = availability.data?.dates?.find(
1348
+ (item) => item.date === day.date
1349
+ );
1350
+ const hasAvailable = entry?.slots?.some((slot) => slot.available) ?? false;
1351
+ const dateValue = parseDateOnly(day.date);
1352
+ const isPast = dateValue ? dateValue < todayDate : false;
1353
+ const isBeyondMax = maxAdvanceDate ? dateValue ? dateValue > maxAdvanceDate : false : false;
1354
+ const weekday = dateValue ? dateValue.getDay() : null;
1355
+ const isDisabledWeekday = weekday !== null ? disabledWeekdaysSet.has(weekday) : false;
1356
+ const isBlocked = Boolean(entry) && !hasAvailable;
1357
+ const isDisabled = !entry || isPast || isBeyondMax || isBlocked || isDisabledWeekday;
1358
+ const isActive = selectedDate === day.date;
1359
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1360
+ "button",
1361
+ {
1362
+ type: "button",
1363
+ className: [
1364
+ "idk-calendar__day",
1365
+ day.isCurrentMonth ? "" : "is-muted",
1366
+ hasAvailable ? "is-available" : "",
1367
+ isBlocked ? "is-blocked" : "",
1368
+ isDisabledWeekday ? "is-disabled" : "",
1369
+ isActive ? "is-active" : ""
1370
+ ].filter(Boolean).join(" "),
1371
+ disabled: isDisabled,
1372
+ onClick: () => {
1373
+ setSelectedDate(day.date);
1374
+ setSelectedTime(null);
1375
+ setSelectedEndTime(null);
1376
+ setBookingError(null);
1377
+ },
1378
+ children: [
1379
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: day.date.split("-")[2] }),
1380
+ showFullyBookedLabel && isBlocked && day.isCurrentMonth ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-calendar__label", children: calendarVariant === "compact" ? "Full" : "Fully booked" }) : null
1381
+ ]
1382
+ },
1383
+ day.date
1384
+ );
1385
+ }) })
1386
+ ]
1387
+ }
1388
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1389
+ enableDatePaging ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-availability__nav", children: [
1390
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1391
+ "button",
1392
+ {
1393
+ type: "button",
1394
+ className: "idk-button idk-button--ghost",
1395
+ onClick: handlePrevDates,
1396
+ disabled: availabilityStartDate <= todayString,
1397
+ children: "Previous"
1398
+ }
1399
+ ),
1400
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1401
+ "button",
1402
+ {
1403
+ type: "button",
1404
+ className: "idk-button idk-button--ghost",
1405
+ onClick: handleNextDates,
1406
+ children: "Next"
1407
+ }
1408
+ )
1409
+ ] }) : null,
1410
+ availability.data.dates.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1411
+ "button",
1412
+ {
1413
+ type: "button",
1414
+ className: entry.date === selectedDate ? "is-active" : void 0,
1415
+ onClick: () => {
1416
+ setSelectedDate(entry.date);
1417
+ setSelectedTime(null);
1418
+ setSelectedEndTime(null);
1419
+ setBookingError(null);
1420
+ },
1421
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-date-chip__day", children: formatDateLabel(entry.date) })
1422
+ },
1423
+ entry.date
1424
+ ))
1425
+ ] }) }),
1426
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-availability__panel", children: [
1427
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-availability__panel-header", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
1428
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-availability__panel-title", children: selectedDate ? formatDateHeading(selectedDate) : availability.data.dates[0]?.date ? formatDateHeading(availability.data.dates[0].date) : "Select a date" }),
1429
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-availability__panel-subtitle", children: timeZoneLabel })
1430
+ ] }) }),
1431
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-availability__slots", children: (() => {
1432
+ const activeEntry = selectedDate ? availability.data.dates.find((entry) => entry.date === selectedDate) : availability.data.dates[0];
1433
+ if (!activeEntry) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "Select a date to see times." });
1434
+ const slots = (activeEntry.slots || []).filter((slot) => slot.available);
1435
+ if (slots.length === 0) {
1436
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "No available times for this date." });
1437
+ }
1438
+ return slots.map((slot, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1439
+ "button",
1440
+ {
1441
+ type: "button",
1442
+ className: slot.time === selectedTime ? "is-active" : void 0,
1443
+ onClick: () => {
1444
+ const date = selectedDate || activeEntry.date;
1445
+ setSelectedDate(date || null);
1446
+ setSelectedTime(slot.time);
1447
+ setSelectedEndTime(slot.endTime);
1448
+ setBookingError(null);
1449
+ if (autoAdvanceOnSelect) {
1450
+ setStep("details");
1451
+ scrollToTop();
1452
+ }
1453
+ },
1454
+ children: formatTimeLabel(slot.time)
1455
+ },
1456
+ `${activeEntry.date}-${slot.time}-${slot.endTime}-${index}`
1457
+ ));
1458
+ })() })
550
1459
  ] })
551
- ]
552
- },
553
- service.id
554
- )) })
555
- ] }),
556
- step === "staff" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__step", children: [
557
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: "Select a team member" }),
558
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-team", children: staffOptions.map((staff) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
559
- "button",
560
- {
561
- type: "button",
562
- className: "idk-card idk-card--clickable",
563
- onClick: () => {
564
- setSelectedStaff(staff);
565
- setStep("time");
566
- },
567
- children: [
568
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__title", children: staff.name }),
569
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__meta", children: staff.role || "Staff" })
570
- ]
571
- },
572
- staff.id
573
- )) }),
574
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
575
- "button",
576
- {
577
- type: "button",
578
- className: "idk-link",
579
- onClick: () => {
580
- setSelectedStaff(null);
581
- setStep("time");
582
- },
583
- children: "Continue without selecting"
584
- }
585
- )
586
- ] }),
587
- step === "time" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__step", children: [
588
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: "Select a time" }),
589
- availability.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "Loading availability..." }) : availability.data ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-availability", children: [
590
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-availability__dates", children: availability.data.dates.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
591
- "button",
592
- {
593
- type: "button",
594
- className: entry.date === selectedDate ? "is-active" : void 0,
595
- onClick: () => {
596
- setSelectedDate(entry.date);
597
- setSelectedTime(null);
598
- setSelectedEndTime(null);
599
- },
600
- children: entry.date
601
- },
602
- entry.date
603
- )) }),
604
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-availability__slots", children: (availability.data.dates.find((entry) => entry.date === selectedDate)?.slots || availability.data.dates[0]?.slots || []).filter((slot) => slot.available).map((slot) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
605
- "button",
606
- {
607
- type: "button",
608
- className: slot.time === selectedTime ? "is-active" : void 0,
609
- onClick: () => {
610
- const date = selectedDate || availability.data?.dates[0]?.date;
611
- setSelectedDate(date || null);
612
- setSelectedTime(slot.time);
613
- setSelectedEndTime(slot.endTime);
614
- setStep("details");
615
- },
616
- children: slot.time
617
- },
618
- `${slot.time}-${slot.endTime}`
619
- )) })
620
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "No availability found." })
621
- ] }),
622
- step === "details" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { className: "idk-form", onSubmit: handleSubmit, children: [
623
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: "Your details" }),
624
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
625
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Name" }),
626
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
627
- "input",
628
- {
629
- value: details.name,
630
- onChange: (event) => setDetails((prev) => ({ ...prev, name: event.target.value })),
631
- required: true
632
- }
633
- )
634
- ] }),
635
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
636
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Email" }),
637
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
638
- "input",
639
- {
640
- type: "email",
641
- value: details.email,
642
- onChange: (event) => setDetails((prev) => ({ ...prev, email: event.target.value })),
643
- required: true
644
- }
645
- )
646
- ] }),
647
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
648
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Phone" }),
649
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
650
- "input",
651
- {
652
- value: details.phone,
653
- onChange: (event) => setDetails((prev) => ({ ...prev, phone: event.target.value }))
654
- }
655
- )
656
- ] }),
657
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
658
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Notes" }),
659
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
660
- "textarea",
661
- {
662
- rows: 4,
663
- value: details.notes,
664
- onChange: (event) => setDetails((prev) => ({ ...prev, notes: event.target.value }))
665
- }
666
- )
667
- ] }),
668
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "submit", className: "idk-button", disabled: createBooking.isPending, children: createBooking.isPending ? "Booking..." : "Confirm booking" })
669
- ] }),
670
- step === "done" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "Booking confirmed. You'll receive a confirmation email shortly." })
671
- ] });
1460
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "No availability found." }),
1461
+ !autoAdvanceOnSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-booking__actions", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1462
+ "button",
1463
+ {
1464
+ type: "button",
1465
+ className: "idk-button",
1466
+ onClick: handleTimeContinue,
1467
+ disabled: !selectedTime || !selectedEndTime,
1468
+ children: "Continue"
1469
+ }
1470
+ ) }) : null
1471
+ ] }),
1472
+ step === "details" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("form", { className: "idk-form", onSubmit: handleSubmit, children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: "Your details" }),
1474
+ bookingError ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-booking__error", children: bookingError }) : null,
1475
+ selectedService && selectedTime ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__summary", children: [
1476
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
1477
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-booking__summary-label", children: "Service" }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: selectedService.name })
1479
+ ] }),
1480
+ selectedStaff ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
1481
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-booking__summary-label", children: "Staff" }),
1482
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: selectedStaff.name })
1483
+ ] }) : null,
1484
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
1485
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-booking__summary-label", children: "When" }),
1486
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { children: [
1487
+ formatDateHeading(selectedDate || ""),
1488
+ " \xB7 ",
1489
+ formatTimeLabel(selectedTime)
1490
+ ] })
1491
+ ] }),
1492
+ selectedService.price ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
1493
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "idk-booking__summary-label", children: "Price" }),
1494
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { children: [
1495
+ "$",
1496
+ (selectedService.price / 100).toFixed(2)
1497
+ ] })
1498
+ ] }) : null
1499
+ ] }) : null,
1500
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "idk-form__field", children: [
1501
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Name" }),
1502
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1503
+ "input",
1504
+ {
1505
+ value: details.name,
1506
+ onChange: (event) => setDetails((prev) => ({ ...prev, name: event.target.value })),
1507
+ required: true
1508
+ }
1509
+ )
1510
+ ] }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "idk-form__field", children: [
1512
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Email" }),
1513
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1514
+ "input",
1515
+ {
1516
+ type: "email",
1517
+ value: details.email,
1518
+ onChange: (event) => setDetails((prev) => ({ ...prev, email: event.target.value })),
1519
+ required: true
1520
+ }
1521
+ )
1522
+ ] }),
1523
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "idk-form__field", children: [
1524
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Phone" }),
1525
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1526
+ "input",
1527
+ {
1528
+ value: details.phone,
1529
+ onChange: (event) => setDetails((prev) => ({ ...prev, phone: event.target.value }))
1530
+ }
1531
+ )
1532
+ ] }),
1533
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "idk-form__field", children: [
1534
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Notes" }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1536
+ "textarea",
1537
+ {
1538
+ rows: 4,
1539
+ value: details.notes,
1540
+ onChange: (event) => setDetails((prev) => ({ ...prev, notes: event.target.value }))
1541
+ }
1542
+ )
1543
+ ] }),
1544
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "submit", className: "idk-button", disabled: createBooking.isPending, children: createBooking.isPending ? "Booking..." : "Confirm booking" })
1545
+ ] }),
1546
+ step === "done" && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "idk-booking__done", children: [
1547
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-booking__done-title", children: "Booking confirmed!" }),
1548
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "idk-booking__done-text", children: "We'll send a confirmation email shortly with your appointment details." }),
1549
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1550
+ "button",
1551
+ {
1552
+ type: "button",
1553
+ className: "idk-link",
1554
+ onClick: () => {
1555
+ setStep("service");
1556
+ setSelectedService(null);
1557
+ resetSelections();
1558
+ setDetails({ name: "", email: "", phone: "", notes: "" });
1559
+ scrollToTop();
1560
+ },
1561
+ children: "Book another appointment"
1562
+ }
1563
+ )
1564
+ ] })
1565
+ ]
1566
+ }
1567
+ );
672
1568
  }
673
1569
 
674
1570
  // src/components/AvailabilityPicker.tsx
675
- var import_react6 = require("react");
676
- var import_jsx_runtime8 = require("react/jsx-runtime");
1571
+ var import_react8 = require("react");
1572
+ var import_jsx_runtime9 = require("react/jsx-runtime");
677
1573
  function AvailabilityPicker({
678
1574
  serviceId,
679
1575
  staffId,
@@ -682,8 +1578,8 @@ function AvailabilityPicker({
682
1578
  className,
683
1579
  onSelect
684
1580
  }) {
685
- const [selectedDate, setSelectedDate] = (0, import_react6.useState)(date || null);
686
- const [selectedTime, setSelectedTime] = (0, import_react6.useState)(null);
1581
+ const [selectedDate, setSelectedDate] = (0, import_react8.useState)(date || null);
1582
+ const [selectedTime, setSelectedTime] = (0, import_react8.useState)(null);
687
1583
  const { data, isLoading } = useAvailability({
688
1584
  serviceId,
689
1585
  staffId,
@@ -691,15 +1587,15 @@ function AvailabilityPicker({
691
1587
  days
692
1588
  });
693
1589
  if (isLoading) {
694
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "Loading availability..." });
1590
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "idk-state", children: "Loading availability..." });
695
1591
  }
696
1592
  if (!data?.dates?.length) {
697
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "No availability found." });
1593
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "idk-state", children: "No availability found." });
698
1594
  }
699
1595
  const activeDate = selectedDate || data.dates[0]?.date;
700
1596
  const activeSlots = data.dates.find((entry) => entry.date === activeDate)?.slots || [];
701
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: ["idk-availability", className].filter(Boolean).join(" "), children: [
702
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-availability__dates", children: data.dates.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1597
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: ["idk-availability", className].filter(Boolean).join(" "), children: [
1598
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "idk-availability__dates", children: data.dates.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
703
1599
  "button",
704
1600
  {
705
1601
  type: "button",
@@ -712,7 +1608,7 @@ function AvailabilityPicker({
712
1608
  },
713
1609
  entry.date
714
1610
  )) }),
715
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-availability__slots", children: activeSlots.filter((slot) => slot.available).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "idk-state", children: "No slots available." }) : activeSlots.filter((slot) => slot.available).map((slot) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1611
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "idk-availability__slots", children: activeSlots.filter((slot) => slot.available).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "idk-state", children: "No slots available." }) : activeSlots.filter((slot) => slot.available).map((slot) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
716
1612
  "button",
717
1613
  {
718
1614
  type: "button",
@@ -735,22 +1631,22 @@ function AvailabilityPicker({
735
1631
  }
736
1632
 
737
1633
  // src/components/ServicePicker.tsx
738
- var import_jsx_runtime9 = require("react/jsx-runtime");
1634
+ var import_jsx_runtime10 = require("react/jsx-runtime");
739
1635
  function ServicePicker({
740
1636
  services,
741
1637
  selectedId,
742
1638
  onSelect,
743
1639
  className
744
1640
  }) {
745
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: services.map((service) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1641
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: services.map((service) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
746
1642
  "button",
747
1643
  {
748
1644
  type: "button",
749
1645
  className: selectedId === service.id ? "is-active" : void 0,
750
1646
  onClick: () => onSelect?.(service),
751
1647
  children: [
752
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: service.name }),
753
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("small", { children: [
1648
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: service.name }),
1649
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("small", { children: [
754
1650
  service.duration,
755
1651
  " min"
756
1652
  ] })
@@ -761,22 +1657,22 @@ function ServicePicker({
761
1657
  }
762
1658
 
763
1659
  // src/components/StaffPicker.tsx
764
- var import_jsx_runtime10 = require("react/jsx-runtime");
1660
+ var import_jsx_runtime11 = require("react/jsx-runtime");
765
1661
  function StaffPicker({
766
1662
  staff,
767
1663
  selectedId,
768
1664
  onSelect,
769
1665
  className
770
1666
  }) {
771
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: staff.map((member) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1667
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: staff.map((member) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
772
1668
  "button",
773
1669
  {
774
1670
  type: "button",
775
1671
  className: selectedId === member.id ? "is-active" : void 0,
776
1672
  onClick: () => onSelect?.(member),
777
1673
  children: [
778
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: member.name }),
779
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { children: member.role || "Staff" })
1674
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: member.name }),
1675
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("small", { children: member.role || "Staff" })
780
1676
  ]
781
1677
  },
782
1678
  member.id
@@ -784,14 +1680,14 @@ function StaffPicker({
784
1680
  }
785
1681
 
786
1682
  // src/components/DatePicker.tsx
787
- var import_jsx_runtime11 = require("react/jsx-runtime");
1683
+ var import_jsx_runtime12 = require("react/jsx-runtime");
788
1684
  function DatePicker({
789
1685
  dates,
790
1686
  selectedDate,
791
1687
  onSelect,
792
1688
  className
793
1689
  }) {
794
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: dates.map((date) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1690
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: dates.map((date) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
795
1691
  "button",
796
1692
  {
797
1693
  type: "button",
@@ -804,14 +1700,14 @@ function DatePicker({
804
1700
  }
805
1701
 
806
1702
  // src/components/TimePicker.tsx
807
- var import_jsx_runtime12 = require("react/jsx-runtime");
1703
+ var import_jsx_runtime13 = require("react/jsx-runtime");
808
1704
  function TimePicker({
809
1705
  slots,
810
1706
  selectedTime,
811
1707
  onSelect,
812
1708
  className
813
1709
  }) {
814
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1710
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
815
1711
  "button",
816
1712
  {
817
1713
  type: "button",