@idkwebsites/components 0.1.5 → 0.1.8

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 });
347
+ }
348
+ let services = data?.services ? [...data.services] : [];
349
+ if (filter) {
350
+ services = services.filter(filter);
351
+ }
352
+ if (sort) {
353
+ services = services.sort(sort);
270
354
  }
271
- if (error || !data?.services?.length) {
272
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "idk-state", children: "No services available." });
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
@@ -301,14 +389,31 @@ function TeamMember({
301
389
  member,
302
390
  className,
303
391
  showRole = true,
304
- showEmail = false
392
+ showEmail = false,
393
+ showBio = false,
394
+ layout = "card",
395
+ imagePosition = "left"
305
396
  }) {
306
397
  const initials = member.name?.split(" ").map((part) => part[0]).slice(0, 2).join("").toUpperCase();
398
+ const avatar = /* @__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 }) });
399
+ if (layout === "profile") {
400
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: ["idk-team__profile", className].filter(Boolean).join(" "), children: [
401
+ imagePosition === "left" ? avatar : null,
402
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "idk-team__info", children: [
403
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "idk-card__title", children: member.name }),
404
+ showRole ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
405
+ showBio && member.bio ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-team__bio", children: member.bio }) : null,
406
+ showEmail ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-card__meta", children: member.email }) : null
407
+ ] }),
408
+ imagePosition === "right" ? avatar : null
409
+ ] });
410
+ }
307
411
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: ["idk-card", className].filter(Boolean).join(" "), children: [
308
412
  /* @__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
413
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "idk-team__info", children: [
310
414
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "idk-card__title", children: member.name }),
311
415
  showRole ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
416
+ showBio && member.bio ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-team__bio", children: member.bio }) : null,
312
417
  showEmail ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "idk-card__meta", children: member.email }) : null
313
418
  ] })
314
419
  ] });
@@ -320,14 +425,33 @@ function TeamGrid({
320
425
  columns = 3,
321
426
  className,
322
427
  showRole,
323
- showEmail
428
+ showEmail,
429
+ showBio,
430
+ layout = "card",
431
+ imagePosition = "left",
432
+ query,
433
+ filter,
434
+ sort,
435
+ limit,
436
+ emptyMessage = "No team members available.",
437
+ loadingMessage = "Loading team..."
324
438
  }) {
325
- const { data, isLoading, error } = useTeam();
439
+ const { data, isLoading, error } = useTeam(query);
326
440
  if (isLoading) {
327
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-state", children: "Loading team..." });
441
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-state", children: loadingMessage });
328
442
  }
329
- if (error || !data?.team?.length) {
330
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-state", children: "No team members available." });
443
+ let members = data?.team ? [...data.team] : [];
444
+ if (filter) {
445
+ members = members.filter(filter);
446
+ }
447
+ if (sort) {
448
+ members = members.sort(sort);
449
+ }
450
+ if (typeof limit === "number") {
451
+ members = members.slice(0, Math.max(0, limit));
452
+ }
453
+ if (error || members.length === 0) {
454
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "idk-state", children: emptyMessage });
331
455
  }
332
456
  const gridStyle = { gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` };
333
457
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -335,12 +459,15 @@ function TeamGrid({
335
459
  {
336
460
  className: ["idk-team", className].filter(Boolean).join(" "),
337
461
  style: gridStyle,
338
- children: data.team.map((member) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
462
+ children: members.map((member) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
339
463
  TeamMember,
340
464
  {
341
465
  member,
342
466
  showRole,
343
- showEmail
467
+ showEmail,
468
+ showBio,
469
+ layout,
470
+ imagePosition
344
471
  },
345
472
  member.id
346
473
  ))
@@ -349,7 +476,7 @@ function TeamGrid({
349
476
  }
350
477
 
351
478
  // src/components/ContactForm.tsx
352
- var import_react4 = require("react");
479
+ var import_react6 = require("react");
353
480
  var import_react_query7 = require("@tanstack/react-query");
354
481
  var import_jsx_runtime6 = require("react/jsx-runtime");
355
482
  var DEFAULT_FIELDS = ["name", "email", "phone", "message"];
@@ -362,7 +489,7 @@ function ContactForm({
362
489
  onError
363
490
  }) {
364
491
  const config = usePlatformConfig();
365
- const [formState, setFormState] = (0, import_react4.useState)({
492
+ const [formState, setFormState] = (0, import_react6.useState)({
366
493
  name: "",
367
494
  email: "",
368
495
  phone: "",
@@ -460,219 +587,678 @@ function ContactForm({
460
587
  }
461
588
 
462
589
  // src/components/BookingWidget.tsx
463
- var import_react5 = require("react");
590
+ var import_react7 = require("react");
464
591
  var import_jsx_runtime7 = require("react/jsx-runtime");
465
592
  function BookingWidget({
466
593
  className,
467
594
  showStaffSelection = true,
468
- onSuccess
595
+ onSuccess,
596
+ services,
597
+ servicesQuery,
598
+ teamQuery,
599
+ serviceFilter,
600
+ teamFilter,
601
+ title = "Book an Appointment",
602
+ subtitle,
603
+ autoAdvanceOnSelect = false,
604
+ scrollToStep = true,
605
+ scrollOffset = 96,
606
+ enableServiceSearch = true,
607
+ serviceSearchPlaceholder = "Search services",
608
+ enableCategoryFilter = false,
609
+ categoryLabel = "All categories",
610
+ serviceListMaxHeight,
611
+ serviceLayout = "list",
612
+ serviceColumns = 2,
613
+ servicePageSize = 10,
614
+ showServicePagination,
615
+ staffLayout = "grid",
616
+ staffColumns = 2,
617
+ showAnyStaffOption = true,
618
+ timeLayout = "split"
469
619
  }) {
470
- const { data: servicesData, isLoading: servicesLoading } = useServices();
471
- const { data: teamData } = useTeam();
620
+ const resolvedServicesQuery = (0, import_react7.useMemo)(
621
+ () => services ? { ...servicesQuery, enabled: false } : servicesQuery,
622
+ [services, servicesQuery]
623
+ );
624
+ const { data: servicesData, isLoading: servicesLoading } = useServices(resolvedServicesQuery);
625
+ const { data: teamData } = useTeam(teamQuery);
472
626
  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)({
627
+ const widgetRef = (0, import_react7.useRef)(null);
628
+ const [step, setStep] = (0, import_react7.useState)("service");
629
+ const [selectedService, setSelectedService] = (0, import_react7.useState)(null);
630
+ const [selectedStaff, setSelectedStaff] = (0, import_react7.useState)(null);
631
+ const [selectedDate, setSelectedDate] = (0, import_react7.useState)(null);
632
+ const [selectedTime, setSelectedTime] = (0, import_react7.useState)(null);
633
+ const [selectedEndTime, setSelectedEndTime] = (0, import_react7.useState)(null);
634
+ const [serviceSearch, setServiceSearch] = (0, import_react7.useState)("");
635
+ const [categoryFilter, setCategoryFilter] = (0, import_react7.useState)("all");
636
+ const [servicePage, setServicePage] = (0, import_react7.useState)(1);
637
+ const [bookingError, setBookingError] = (0, import_react7.useState)(null);
638
+ const [details, setDetails] = (0, import_react7.useState)({
480
639
  name: "",
481
640
  email: "",
482
641
  phone: "",
483
642
  notes: ""
484
643
  });
485
- const staffOptions = (0, import_react5.useMemo)(() => {
644
+ const servicesList = (0, import_react7.useMemo)(() => {
645
+ const list = services ? [...services] : servicesData?.services ? [...servicesData.services] : [];
646
+ return serviceFilter ? list.filter(serviceFilter) : list;
647
+ }, [services, servicesData, serviceFilter]);
648
+ const categories = (0, import_react7.useMemo)(() => {
649
+ const set = /* @__PURE__ */ new Set();
650
+ servicesList.forEach((service) => {
651
+ if (service.category) {
652
+ set.add(service.category);
653
+ }
654
+ });
655
+ return Array.from(set).sort();
656
+ }, [servicesList]);
657
+ const filteredServices = (0, import_react7.useMemo)(() => {
658
+ const search = serviceSearch.trim().toLowerCase();
659
+ return servicesList.filter((service) => {
660
+ const matchesSearch = !search || service.name.toLowerCase().includes(search) || (service.description || "").toLowerCase().includes(search) || (service.category || "").toLowerCase().includes(search);
661
+ const matchesCategory = categoryFilter === "all" ? true : service.category === categoryFilter;
662
+ return matchesSearch && matchesCategory;
663
+ });
664
+ }, [servicesList, serviceSearch, categoryFilter]);
665
+ (0, import_react7.useEffect)(() => {
666
+ setServicePage(1);
667
+ }, [serviceSearch, categoryFilter]);
668
+ const pagedServices = (0, import_react7.useMemo)(() => {
669
+ const pageSize = Math.max(1, servicePageSize);
670
+ return filteredServices.slice(0, pageSize * servicePage);
671
+ }, [filteredServices, servicePage, servicePageSize]);
672
+ const hasMoreServices = filteredServices.length > pagedServices.length;
673
+ const shouldPaginate = showServicePagination ?? hasMoreServices;
674
+ const staffOptions = (0, import_react7.useMemo)(() => {
486
675
  if (!selectedService) return [];
487
676
  if (selectedService.assignedStaff && selectedService.assignedStaff.length > 0) {
488
- return selectedService.assignedStaff;
677
+ const assigned = selectedService.assignedStaff;
678
+ return teamFilter ? assigned.filter(teamFilter) : assigned;
489
679
  }
490
- return teamData?.team || [];
491
- }, [selectedService, teamData]);
680
+ const teamList = teamData?.team || [];
681
+ return teamFilter ? teamList.filter(teamFilter) : teamList;
682
+ }, [selectedService, teamData, teamFilter]);
492
683
  const availability = useAvailability({
493
684
  serviceId: selectedService?.id || "",
494
685
  staffId: selectedStaff?.id,
495
686
  days: 7,
496
687
  enabled: Boolean(selectedService)
497
688
  });
689
+ const timeZone = availability.data?.timeZone || "America/Chicago";
690
+ const safeTimeZone = (0, import_react7.useMemo)(() => {
691
+ if (!timeZone) return void 0;
692
+ try {
693
+ new Intl.DateTimeFormat("en-US", { timeZone }).format(/* @__PURE__ */ new Date());
694
+ return timeZone;
695
+ } catch {
696
+ return void 0;
697
+ }
698
+ }, [timeZone]);
699
+ const timeZoneLabel = (0, import_react7.useMemo)(() => {
700
+ if (timeZone) return timeZone;
701
+ try {
702
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
703
+ } catch {
704
+ return "Local time";
705
+ }
706
+ }, [timeZone]);
707
+ const dateLabelFormatter = (0, import_react7.useMemo)(
708
+ () => new Intl.DateTimeFormat("en-US", {
709
+ weekday: "short",
710
+ month: "short",
711
+ day: "numeric"
712
+ }),
713
+ []
714
+ );
715
+ const dateHeadingFormatter = (0, import_react7.useMemo)(
716
+ () => new Intl.DateTimeFormat("en-US", {
717
+ weekday: "long",
718
+ month: "long",
719
+ day: "numeric"
720
+ }),
721
+ []
722
+ );
723
+ const timeFormatter = (0, import_react7.useMemo)(
724
+ () => new Intl.DateTimeFormat("en-US", {
725
+ timeZone: safeTimeZone,
726
+ hour: "numeric",
727
+ minute: "2-digit"
728
+ }),
729
+ [safeTimeZone]
730
+ );
498
731
  const requiresStaff = showStaffSelection || selectedService?.requiresStaffSelection || selectedService?.schedulingType === "customer-choice";
732
+ const canSkipStaff = !selectedService?.requiresStaffSelection;
733
+ const totalSteps = requiresStaff ? 4 : 3;
734
+ const stepNumber = (() => {
735
+ if (step === "service") return 1;
736
+ if (step === "staff") return 2;
737
+ if (step === "time") return requiresStaff ? 3 : 2;
738
+ if (step === "details") return requiresStaff ? 4 : 3;
739
+ return totalSteps;
740
+ })();
741
+ const scrollToTop = (0, import_react7.useCallback)(() => {
742
+ if (!scrollToStep || !widgetRef.current) return;
743
+ const top = widgetRef.current.getBoundingClientRect().top + window.scrollY;
744
+ window.scrollTo({
745
+ top: Math.max(0, top - scrollOffset),
746
+ behavior: "smooth"
747
+ });
748
+ }, [scrollToStep, scrollOffset]);
749
+ const resetSelections = () => {
750
+ setSelectedStaff(null);
751
+ setSelectedDate(null);
752
+ setSelectedTime(null);
753
+ setSelectedEndTime(null);
754
+ setBookingError(null);
755
+ };
756
+ (0, import_react7.useEffect)(() => {
757
+ if (step !== "time") return;
758
+ if (!availability.data?.dates?.length) return;
759
+ if (selectedDate) return;
760
+ setSelectedDate(availability.data.dates[0].date);
761
+ }, [step, availability.data, selectedDate]);
762
+ const parseDateOnly = (value) => {
763
+ if (!value) return null;
764
+ const normalized = value.includes("T") ? value.split("T")[0] : value;
765
+ const parsed = /* @__PURE__ */ new Date(`${normalized}T00:00:00`);
766
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
767
+ };
768
+ const parseDateTime = (value, fallbackDate) => {
769
+ if (!value) return null;
770
+ const direct = new Date(value);
771
+ if (!Number.isNaN(direct.getTime())) return direct;
772
+ if (fallbackDate) {
773
+ const normalized = fallbackDate.includes("T") ? fallbackDate.split("T")[0] : fallbackDate;
774
+ const combined = /* @__PURE__ */ new Date(`${normalized}T${value}`);
775
+ if (!Number.isNaN(combined.getTime())) return combined;
776
+ }
777
+ return null;
778
+ };
779
+ const formatDateLabel = (date) => {
780
+ const parsed = parseDateOnly(date);
781
+ if (!parsed) return date;
782
+ try {
783
+ return dateLabelFormatter.format(parsed);
784
+ } catch {
785
+ return date;
786
+ }
787
+ };
788
+ const formatDateHeading = (date) => {
789
+ const parsed = parseDateOnly(date);
790
+ if (!parsed) return date;
791
+ try {
792
+ return dateHeadingFormatter.format(parsed);
793
+ } catch {
794
+ return date;
795
+ }
796
+ };
797
+ const formatTimeLabel = (iso) => {
798
+ const parsed = parseDateTime(iso, selectedDate);
799
+ if (!parsed) return iso;
800
+ try {
801
+ return timeFormatter.format(parsed);
802
+ } catch {
803
+ return parsed.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
804
+ }
805
+ };
806
+ const formatDuration = (minutes) => {
807
+ if (!Number.isFinite(minutes)) return "";
808
+ if (minutes < 60) return `${minutes} min`;
809
+ const hours = Math.floor(minutes / 60);
810
+ const remaining = minutes % 60;
811
+ return remaining === 0 ? `${hours} hr${hours > 1 ? "s" : ""}` : `${hours} hr ${remaining} min`;
812
+ };
813
+ const handleBack = () => {
814
+ if (step === "service") return;
815
+ if (step === "staff") {
816
+ setStep("service");
817
+ } else if (step === "time") {
818
+ if (requiresStaff) {
819
+ setStep("staff");
820
+ } else {
821
+ setStep("service");
822
+ }
823
+ } else if (step === "details") {
824
+ setStep("time");
825
+ } else if (step === "done") {
826
+ setStep("details");
827
+ }
828
+ setBookingError(null);
829
+ scrollToTop();
830
+ };
831
+ const handleServiceContinue = () => {
832
+ if (!selectedService) return;
833
+ if (requiresStaff) {
834
+ setStep("staff");
835
+ } else {
836
+ setStep("time");
837
+ }
838
+ scrollToTop();
839
+ };
840
+ const handleStaffContinue = () => {
841
+ if (!canSkipStaff && !selectedStaff) return;
842
+ setStep("time");
843
+ scrollToTop();
844
+ };
845
+ const handleTimeContinue = () => {
846
+ if (!selectedTime || !selectedEndTime) return;
847
+ setStep("details");
848
+ scrollToTop();
849
+ };
499
850
  const handleSubmit = async (event) => {
500
851
  event.preventDefault();
501
852
  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);
853
+ const start = parseDateTime(selectedTime, selectedDate);
854
+ const end = parseDateTime(selectedEndTime, selectedDate);
855
+ if (!start || !end) {
856
+ setBookingError("Please select a valid time slot.");
857
+ return;
858
+ }
859
+ try {
860
+ setBookingError(null);
861
+ const result = await createBooking.mutateAsync({
862
+ serviceId: selectedService.id,
863
+ staffId: selectedStaff?.id,
864
+ startTime: start.toISOString(),
865
+ endTime: end.toISOString(),
866
+ customerName: details.name,
867
+ customerEmail: details.email,
868
+ customerPhone: details.phone || void 0,
869
+ customerNotes: details.notes || void 0
870
+ });
871
+ setStep("done");
872
+ onSuccess?.(result?.booking);
873
+ scrollToTop();
874
+ } catch (error) {
875
+ setBookingError(
876
+ error instanceof Error ? error.message : "Unable to complete booking. Please try again."
877
+ );
878
+ }
516
879
  };
517
- if (servicesLoading) {
880
+ const isServicesLoading = services ? false : servicesLoading;
881
+ if (isServicesLoading) {
518
882
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "Loading booking options..." });
519
883
  }
520
- if (!servicesData?.services?.length) {
884
+ if (!servicesList.length) {
521
885
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "No services available." });
522
886
  }
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");
887
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
888
+ "div",
889
+ {
890
+ ref: widgetRef,
891
+ className: ["idk-booking", className].filter(Boolean).join(" "),
892
+ children: [
893
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__header", children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__title-wrap", children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "idk-booking__title", children: title }),
896
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "idk-booking__subtitle", children: subtitle }) : null
897
+ ] }),
898
+ step !== "service" && step !== "done" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "idk-link", onClick: handleBack, children: "Back" }) : null
899
+ ] }),
900
+ step !== "done" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__progress", children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
902
+ "Step ",
903
+ stepNumber,
904
+ " of ",
905
+ totalSteps
906
+ ] }),
907
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-booking__bar", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
908
+ "div",
909
+ {
910
+ className: "idk-booking__bar-fill",
911
+ style: { width: `${stepNumber / totalSteps * 100}%` }
537
912
  }
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"
913
+ ) })
914
+ ] }) : null,
915
+ step === "service" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__step", children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { children: "Select a service" }),
917
+ enableServiceSearch || enableCategoryFilter ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__filters", children: [
918
+ enableServiceSearch ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
919
+ "input",
920
+ {
921
+ type: "search",
922
+ value: serviceSearch,
923
+ onChange: (event) => setServiceSearch(event.target.value),
924
+ placeholder: serviceSearchPlaceholder
925
+ }
926
+ ) : null,
927
+ enableCategoryFilter && categories.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
928
+ "select",
929
+ {
930
+ value: categoryFilter,
931
+ onChange: (event) => setCategoryFilter(event.target.value),
932
+ children: [
933
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "all", children: categoryLabel }),
934
+ categories.map((category) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: category, children: category }, category))
935
+ ]
936
+ }
937
+ ) : null
938
+ ] }) : null,
939
+ filteredServices.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "No services match your filters." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
940
+ "div",
941
+ {
942
+ className: [
943
+ "idk-services",
944
+ serviceLayout === "grid" ? "idk-services--grid" : "idk-services--list"
945
+ ].join(" "),
946
+ style: {
947
+ ...serviceLayout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, serviceColumns)}, minmax(0, 1fr))` } : void 0,
948
+ ...serviceListMaxHeight ? { maxHeight: serviceListMaxHeight, overflow: "auto" } : void 0
949
+ },
950
+ children: pagedServices.map((service) => {
951
+ const isSelected = selectedService?.id === service.id;
952
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
953
+ "button",
954
+ {
955
+ type: "button",
956
+ className: ["idk-card", "idk-card--clickable", isSelected ? "is-active" : ""].join(" "),
957
+ onClick: () => {
958
+ setSelectedService(service);
959
+ resetSelections();
960
+ if (autoAdvanceOnSelect) {
961
+ if (requiresStaff) {
962
+ setStep("staff");
963
+ } else {
964
+ setStep("time");
965
+ }
966
+ scrollToTop();
967
+ }
968
+ },
969
+ children: [
970
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-card__header", children: [
971
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__title", children: service.name }),
972
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "idk-card__aside", children: [
973
+ isSelected ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__check", children: "\u2713" }) : null,
974
+ typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "idk-card__price", children: [
975
+ "$",
976
+ (service.price / 100).toFixed(2)
977
+ ] }) : null
978
+ ] })
979
+ ] }),
980
+ service.description ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-card__description", children: service.description }) : null,
981
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-card__meta", children: [
982
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: formatDuration(service.duration) }),
983
+ service.category ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-pill", children: service.category }) : null
984
+ ] })
985
+ ]
986
+ },
987
+ service.id
988
+ );
989
+ })
990
+ }
991
+ ),
992
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__actions", children: [
993
+ shouldPaginate ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
994
+ "button",
995
+ {
996
+ type: "button",
997
+ className: "idk-button idk-button--ghost",
998
+ onClick: () => setServicePage((prev) => prev + 1),
999
+ disabled: !hasMoreServices,
1000
+ children: hasMoreServices ? "Show more services" : "All services shown"
1001
+ }
1002
+ ) : null,
1003
+ selectedService && !autoAdvanceOnSelect ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "idk-button", onClick: handleServiceContinue, children: "Continue" }) : null
1004
+ ] })
1005
+ ] }),
1006
+ step === "staff" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__step", children: [
1007
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { children: "Select a team member" }),
1008
+ staffOptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "No team members available." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1009
+ "div",
1010
+ {
1011
+ className: [
1012
+ "idk-team",
1013
+ staffLayout === "grid" ? "idk-team--grid" : "idk-team--list"
1014
+ ].join(" "),
1015
+ style: staffLayout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, staffColumns)}, minmax(0, 1fr))` } : void 0,
1016
+ children: [
1017
+ showAnyStaffOption && canSkipStaff ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1018
+ "button",
1019
+ {
1020
+ type: "button",
1021
+ className: ["idk-card", "idk-card--clickable", !selectedStaff ? "is-active" : ""].join(" "),
1022
+ onClick: () => {
1023
+ setSelectedStaff(null);
1024
+ setSelectedDate(null);
1025
+ setSelectedTime(null);
1026
+ setSelectedEndTime(null);
1027
+ if (autoAdvanceOnSelect) {
1028
+ setStep("time");
1029
+ scrollToTop();
1030
+ }
1031
+ },
1032
+ children: [
1033
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-card__header", children: [
1034
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__title", children: "Any Available" }),
1035
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__badge", children: "Recommended" })
1036
+ ] }),
1037
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "idk-card__description", children: "We'll match you with the first available stylist." })
1038
+ ]
1039
+ },
1040
+ "any-staff"
1041
+ ) : null,
1042
+ staffOptions.map((staff) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1043
+ "button",
1044
+ {
1045
+ type: "button",
1046
+ className: ["idk-card", "idk-card--clickable", selectedStaff?.id === staff.id ? "is-active" : ""].join(" "),
1047
+ onClick: () => {
1048
+ setSelectedStaff(staff);
1049
+ setSelectedDate(null);
1050
+ setSelectedTime(null);
1051
+ setSelectedEndTime(null);
1052
+ setBookingError(null);
1053
+ if (autoAdvanceOnSelect) {
1054
+ setStep("time");
1055
+ scrollToTop();
1056
+ }
1057
+ },
1058
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-team__card", children: [
1059
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-team__avatar", children: staff.photo?.url ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: staff.photo.url, alt: staff.name }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: staff.name.split(" ").map((part) => part[0]).slice(0, 2).join("").toUpperCase() }) }),
1060
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1061
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__title", children: staff.name }),
1062
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-card__meta", children: staff.role || "Staff" }),
1063
+ staff.bio ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "idk-card__description", children: staff.bio }) : null
1064
+ ] })
1065
+ ] })
1066
+ },
1067
+ staff.id
1068
+ ))
1069
+ ]
1070
+ }
1071
+ ),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__actions", children: [
1073
+ canSkipStaff ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1074
+ "button",
1075
+ {
1076
+ type: "button",
1077
+ className: "idk-link",
1078
+ onClick: () => {
1079
+ setSelectedStaff(null);
1080
+ setStep("time");
1081
+ scrollToTop();
1082
+ },
1083
+ children: "Continue without selecting"
1084
+ }
1085
+ ) : null,
1086
+ !autoAdvanceOnSelect ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1087
+ "button",
1088
+ {
1089
+ type: "button",
1090
+ className: "idk-button",
1091
+ onClick: handleStaffContinue,
1092
+ disabled: !canSkipStaff && !selectedStaff,
1093
+ children: "Continue"
1094
+ }
1095
+ ) : null
1096
+ ] })
1097
+ ] }),
1098
+ step === "time" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__step", children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { children: "Select a time" }),
1100
+ 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", timeLayout === "split" ? "idk-availability--split" : ""].join(" "), children: [
1101
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-availability__dates", children: availability.data.dates.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1102
+ "button",
1103
+ {
1104
+ type: "button",
1105
+ className: entry.date === selectedDate ? "is-active" : void 0,
1106
+ onClick: () => {
1107
+ setSelectedDate(entry.date);
1108
+ setSelectedTime(null);
1109
+ setSelectedEndTime(null);
1110
+ setBookingError(null);
1111
+ },
1112
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-date-chip__day", children: formatDateLabel(entry.date) })
1113
+ },
1114
+ entry.date
1115
+ )) }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-availability__panel", children: [
1117
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-availability__panel-header", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1118
+ /* @__PURE__ */ (0, import_jsx_runtime7.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" }),
1119
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-availability__panel-subtitle", children: timeZoneLabel })
1120
+ ] }) }),
1121
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-availability__slots", children: (() => {
1122
+ const activeEntry = availability.data.dates.find((entry) => entry.date === selectedDate) || availability.data.dates[0];
1123
+ if (!activeEntry) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "Select a date to see times." });
1124
+ const slots = (activeEntry.slots || []).filter((slot) => slot.available);
1125
+ if (slots.length === 0) {
1126
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "No available times for this date." });
1127
+ }
1128
+ return slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1129
+ "button",
1130
+ {
1131
+ type: "button",
1132
+ className: slot.time === selectedTime ? "is-active" : void 0,
1133
+ onClick: () => {
1134
+ const date = selectedDate || activeEntry.date;
1135
+ setSelectedDate(date || null);
1136
+ setSelectedTime(slot.time);
1137
+ setSelectedEndTime(slot.endTime);
1138
+ setBookingError(null);
1139
+ if (autoAdvanceOnSelect) {
1140
+ setStep("details");
1141
+ scrollToTop();
1142
+ }
1143
+ },
1144
+ children: formatTimeLabel(slot.time)
1145
+ },
1146
+ `${slot.time}-${slot.endTime}`
1147
+ ));
1148
+ })() })
550
1149
  ] })
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
- ] });
1150
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-state", children: "No availability found." }),
1151
+ !autoAdvanceOnSelect ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-booking__actions", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1152
+ "button",
1153
+ {
1154
+ type: "button",
1155
+ className: "idk-button",
1156
+ onClick: handleTimeContinue,
1157
+ disabled: !selectedTime || !selectedEndTime,
1158
+ children: "Continue"
1159
+ }
1160
+ ) }) : null
1161
+ ] }),
1162
+ step === "details" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { className: "idk-form", onSubmit: handleSubmit, children: [
1163
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { children: "Your details" }),
1164
+ bookingError ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-booking__error", children: bookingError }) : null,
1165
+ selectedService && selectedTime ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__summary", children: [
1166
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1167
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-booking__summary-label", children: "Service" }),
1168
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: selectedService.name })
1169
+ ] }),
1170
+ selectedStaff ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1171
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-booking__summary-label", children: "Staff" }),
1172
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: selectedStaff.name })
1173
+ ] }) : null,
1174
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1175
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-booking__summary-label", children: "When" }),
1176
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
1177
+ formatDateHeading(selectedDate || ""),
1178
+ " \xB7 ",
1179
+ formatTimeLabel(selectedTime)
1180
+ ] })
1181
+ ] }),
1182
+ selectedService.price ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1183
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "idk-booking__summary-label", children: "Price" }),
1184
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
1185
+ "$",
1186
+ (selectedService.price / 100).toFixed(2)
1187
+ ] })
1188
+ ] }) : null
1189
+ ] }) : null,
1190
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
1191
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Name" }),
1192
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1193
+ "input",
1194
+ {
1195
+ value: details.name,
1196
+ onChange: (event) => setDetails((prev) => ({ ...prev, name: event.target.value })),
1197
+ required: true
1198
+ }
1199
+ )
1200
+ ] }),
1201
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
1202
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Email" }),
1203
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1204
+ "input",
1205
+ {
1206
+ type: "email",
1207
+ value: details.email,
1208
+ onChange: (event) => setDetails((prev) => ({ ...prev, email: event.target.value })),
1209
+ required: true
1210
+ }
1211
+ )
1212
+ ] }),
1213
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Phone" }),
1215
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1216
+ "input",
1217
+ {
1218
+ value: details.phone,
1219
+ onChange: (event) => setDetails((prev) => ({ ...prev, phone: event.target.value }))
1220
+ }
1221
+ )
1222
+ ] }),
1223
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "idk-form__field", children: [
1224
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Notes" }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1226
+ "textarea",
1227
+ {
1228
+ rows: 4,
1229
+ value: details.notes,
1230
+ onChange: (event) => setDetails((prev) => ({ ...prev, notes: event.target.value }))
1231
+ }
1232
+ )
1233
+ ] }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "submit", className: "idk-button", disabled: createBooking.isPending, children: createBooking.isPending ? "Booking..." : "Confirm booking" })
1235
+ ] }),
1236
+ step === "done" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "idk-booking__done", children: [
1237
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "idk-booking__done-title", children: "Booking confirmed!" }),
1238
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "idk-booking__done-text", children: "We'll send a confirmation email shortly with your appointment details." }),
1239
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1240
+ "button",
1241
+ {
1242
+ type: "button",
1243
+ className: "idk-link",
1244
+ onClick: () => {
1245
+ setStep("service");
1246
+ setSelectedService(null);
1247
+ resetSelections();
1248
+ setDetails({ name: "", email: "", phone: "", notes: "" });
1249
+ scrollToTop();
1250
+ },
1251
+ children: "Book another appointment"
1252
+ }
1253
+ )
1254
+ ] })
1255
+ ]
1256
+ }
1257
+ );
672
1258
  }
673
1259
 
674
1260
  // src/components/AvailabilityPicker.tsx
675
- var import_react6 = require("react");
1261
+ var import_react8 = require("react");
676
1262
  var import_jsx_runtime8 = require("react/jsx-runtime");
677
1263
  function AvailabilityPicker({
678
1264
  serviceId,
@@ -682,8 +1268,8 @@ function AvailabilityPicker({
682
1268
  className,
683
1269
  onSelect
684
1270
  }) {
685
- const [selectedDate, setSelectedDate] = (0, import_react6.useState)(date || null);
686
- const [selectedTime, setSelectedTime] = (0, import_react6.useState)(null);
1271
+ const [selectedDate, setSelectedDate] = (0, import_react8.useState)(date || null);
1272
+ const [selectedTime, setSelectedTime] = (0, import_react8.useState)(null);
687
1273
  const { data, isLoading } = useAvailability({
688
1274
  serviceId,
689
1275
  staffId,