@idkwebsites/components 0.1.7 → 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.js CHANGED
@@ -97,29 +97,97 @@ function useTenant() {
97
97
  }
98
98
 
99
99
  // src/core/hooks/useServices.ts
100
+ import { useMemo } from "react";
100
101
  import { useQuery as useQuery2 } from "@tanstack/react-query";
101
- function useServices() {
102
+ function buildQuery(params) {
103
+ if (!params) return "";
104
+ const searchParams = new URLSearchParams();
105
+ const categories = Array.isArray(params.category) ? params.category : params.category ? [params.category] : [];
106
+ if (categories.length) searchParams.set("category", categories.join(","));
107
+ if (params.staffId) searchParams.set("staffId", params.staffId);
108
+ if (typeof params.isActive === "boolean") searchParams.set("isActive", String(params.isActive));
109
+ if (typeof params.minPrice === "number") searchParams.set("minPrice", String(params.minPrice));
110
+ if (typeof params.maxPrice === "number") searchParams.set("maxPrice", String(params.maxPrice));
111
+ if (params.search) searchParams.set("search", params.search);
112
+ if (params.ids?.length) searchParams.set("ids", params.ids.join(","));
113
+ if (typeof params.limit === "number") searchParams.set("limit", String(params.limit));
114
+ if (params.sort) searchParams.set("sort", params.sort);
115
+ if (params.order) searchParams.set("order", params.order);
116
+ return searchParams.toString();
117
+ }
118
+ function useServices(params) {
102
119
  const config = usePlatformConfig();
120
+ const queryString = useMemo(() => buildQuery(params), [
121
+ params?.category,
122
+ params?.staffId,
123
+ params?.isActive,
124
+ params?.minPrice,
125
+ params?.maxPrice,
126
+ params?.search,
127
+ params?.ids,
128
+ params?.limit,
129
+ params?.sort,
130
+ params?.order
131
+ ]);
132
+ const enabled = params?.enabled ?? true;
103
133
  return useQuery2({
104
- queryKey: ["idk", "services"],
105
- queryFn: () => apiRequest(config, "/services")
134
+ queryKey: ["idk", "services", queryString],
135
+ queryFn: () => apiRequest(
136
+ config,
137
+ queryString ? `/services?${queryString}` : "/services"
138
+ ),
139
+ enabled
106
140
  });
107
141
  }
108
142
 
109
143
  // src/core/hooks/useTeam.ts
144
+ import { useMemo as useMemo2 } from "react";
110
145
  import { useQuery as useQuery3 } from "@tanstack/react-query";
111
- function useTeam() {
146
+ function buildQuery2(params) {
147
+ if (!params) return "";
148
+ const searchParams = new URLSearchParams();
149
+ const roles = Array.isArray(params.role) ? params.role : params.role ? [params.role] : [];
150
+ if (roles.length) searchParams.set("role", roles.join(","));
151
+ if (typeof params.isActive === "boolean") searchParams.set("isActive", String(params.isActive));
152
+ if (typeof params.acceptsNewBookings === "boolean") {
153
+ searchParams.set("acceptsNewBookings", String(params.acceptsNewBookings));
154
+ }
155
+ if (params.search) searchParams.set("search", params.search);
156
+ if (params.ids?.length) searchParams.set("ids", params.ids.join(","));
157
+ if (params.serviceId) searchParams.set("serviceId", params.serviceId);
158
+ if (typeof params.limit === "number") searchParams.set("limit", String(params.limit));
159
+ if (params.sort) searchParams.set("sort", params.sort);
160
+ if (params.order) searchParams.set("order", params.order);
161
+ return searchParams.toString();
162
+ }
163
+ function useTeam(params) {
112
164
  const config = usePlatformConfig();
165
+ const queryString = useMemo2(() => buildQuery2(params), [
166
+ params?.role,
167
+ params?.isActive,
168
+ params?.acceptsNewBookings,
169
+ params?.search,
170
+ params?.ids,
171
+ params?.serviceId,
172
+ params?.limit,
173
+ params?.sort,
174
+ params?.order
175
+ ]);
176
+ const enabled = params?.enabled ?? true;
113
177
  return useQuery3({
114
- queryKey: ["idk", "team"],
115
- queryFn: () => apiRequest(config, "/team")
178
+ queryKey: ["idk", "team", queryString],
179
+ queryFn: () => apiRequest(
180
+ config,
181
+ queryString ? `/team?${queryString}` : "/team"
182
+ ),
183
+ enabled
116
184
  });
117
185
  }
118
186
 
119
187
  // src/core/hooks/useAvailability.ts
120
- import { useMemo } from "react";
188
+ import { useMemo as useMemo3 } from "react";
121
189
  import { useQuery as useQuery4 } from "@tanstack/react-query";
122
- function buildQuery(params) {
190
+ function buildQuery3(params) {
123
191
  const searchParams = new URLSearchParams();
124
192
  searchParams.set("serviceId", params.serviceId);
125
193
  if (params.staffId) searchParams.set("staffId", params.staffId);
@@ -131,8 +199,8 @@ function buildQuery(params) {
131
199
  }
132
200
  function useAvailability(params) {
133
201
  const config = usePlatformConfig();
134
- const queryString = useMemo(
135
- () => buildQuery(params),
202
+ const queryString = useMemo3(
203
+ () => buildQuery3(params),
136
204
  [params.serviceId, params.staffId, params.startDate, params.endDate, params.date, params.days]
137
205
  );
138
206
  const enabled = params.enabled ?? Boolean(params.serviceId);
@@ -182,6 +250,7 @@ function ServiceCard({
182
250
  className,
183
251
  showDescription = true,
184
252
  showPrice = true,
253
+ showImage = false,
185
254
  onSelect
186
255
  }) {
187
256
  const handleClick = () => {
@@ -195,6 +264,7 @@ function ServiceCard({
195
264
  role: onSelect ? "button" : void 0,
196
265
  tabIndex: onSelect ? 0 : void 0,
197
266
  children: [
267
+ showImage && service.image?.url ? /* @__PURE__ */ jsx2("div", { className: "idk-card__media", children: /* @__PURE__ */ jsx2("img", { src: service.image.url, alt: service.name }) }) : null,
198
268
  /* @__PURE__ */ jsxs("div", { className: "idk-card__header", children: [
199
269
  /* @__PURE__ */ jsx2("h3", { className: "idk-card__title", children: service.name }),
200
270
  showPrice && typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ jsxs("span", { className: "idk-card__price", children: [
@@ -223,14 +293,31 @@ function ServicesList({
223
293
  className,
224
294
  showDescription,
225
295
  showPrice,
296
+ showImage,
297
+ query,
298
+ filter,
299
+ sort,
300
+ limit,
301
+ emptyMessage = "No services available.",
302
+ loadingMessage = "Loading services...",
226
303
  onSelect
227
304
  }) {
228
- const { data, isLoading, error } = useServices();
305
+ const { data, isLoading, error } = useServices(query);
229
306
  if (isLoading) {
230
- return /* @__PURE__ */ jsx3("div", { className: "idk-state", children: "Loading services..." });
307
+ return /* @__PURE__ */ jsx3("div", { className: "idk-state", children: loadingMessage });
231
308
  }
232
- if (error || !data?.services?.length) {
233
- return /* @__PURE__ */ jsx3("div", { className: "idk-state", children: "No services available." });
309
+ let services = data?.services ? [...data.services] : [];
310
+ if (filter) {
311
+ services = services.filter(filter);
312
+ }
313
+ if (sort) {
314
+ services = services.sort(sort);
315
+ }
316
+ if (typeof limit === "number") {
317
+ services = services.slice(0, Math.max(0, limit));
318
+ }
319
+ if (error || services.length === 0) {
320
+ return /* @__PURE__ */ jsx3("div", { className: "idk-state", children: emptyMessage });
234
321
  }
235
322
  const gridStyle = layout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` } : void 0;
236
323
  return /* @__PURE__ */ jsx3(
@@ -242,12 +329,13 @@ function ServicesList({
242
329
  className
243
330
  ].filter(Boolean).join(" "),
244
331
  style: gridStyle,
245
- children: data.services.map((service) => /* @__PURE__ */ jsx3(
332
+ children: services.map((service) => /* @__PURE__ */ jsx3(
246
333
  ServiceCard,
247
334
  {
248
335
  service,
249
336
  showDescription,
250
337
  showPrice,
338
+ showImage,
251
339
  onSelect
252
340
  },
253
341
  service.id
@@ -262,14 +350,31 @@ function TeamMember({
262
350
  member,
263
351
  className,
264
352
  showRole = true,
265
- showEmail = false
353
+ showEmail = false,
354
+ showBio = false,
355
+ layout = "card",
356
+ imagePosition = "left"
266
357
  }) {
267
358
  const initials = member.name?.split(" ").map((part) => part[0]).slice(0, 2).join("").toUpperCase();
359
+ const avatar = /* @__PURE__ */ jsx4("div", { className: "idk-team__avatar", children: member.photo?.url ? /* @__PURE__ */ jsx4("img", { src: member.photo.url, alt: member.name }) : /* @__PURE__ */ jsx4("span", { children: initials }) });
360
+ if (layout === "profile") {
361
+ return /* @__PURE__ */ jsxs2("div", { className: ["idk-team__profile", className].filter(Boolean).join(" "), children: [
362
+ imagePosition === "left" ? avatar : null,
363
+ /* @__PURE__ */ jsxs2("div", { className: "idk-team__info", children: [
364
+ /* @__PURE__ */ jsx4("h3", { className: "idk-card__title", children: member.name }),
365
+ showRole ? /* @__PURE__ */ jsx4("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
366
+ showBio && member.bio ? /* @__PURE__ */ jsx4("p", { className: "idk-team__bio", children: member.bio }) : null,
367
+ showEmail ? /* @__PURE__ */ jsx4("p", { className: "idk-card__meta", children: member.email }) : null
368
+ ] }),
369
+ imagePosition === "right" ? avatar : null
370
+ ] });
371
+ }
268
372
  return /* @__PURE__ */ jsxs2("div", { className: ["idk-card", className].filter(Boolean).join(" "), children: [
269
373
  /* @__PURE__ */ jsx4("div", { className: "idk-team__avatar", children: member.photo?.url ? /* @__PURE__ */ jsx4("img", { src: member.photo.url, alt: member.name }) : /* @__PURE__ */ jsx4("span", { children: initials }) }),
270
374
  /* @__PURE__ */ jsxs2("div", { className: "idk-team__info", children: [
271
375
  /* @__PURE__ */ jsx4("h3", { className: "idk-card__title", children: member.name }),
272
376
  showRole ? /* @__PURE__ */ jsx4("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
377
+ showBio && member.bio ? /* @__PURE__ */ jsx4("p", { className: "idk-team__bio", children: member.bio }) : null,
273
378
  showEmail ? /* @__PURE__ */ jsx4("p", { className: "idk-card__meta", children: member.email }) : null
274
379
  ] })
275
380
  ] });
@@ -281,14 +386,33 @@ function TeamGrid({
281
386
  columns = 3,
282
387
  className,
283
388
  showRole,
284
- showEmail
389
+ showEmail,
390
+ showBio,
391
+ layout = "card",
392
+ imagePosition = "left",
393
+ query,
394
+ filter,
395
+ sort,
396
+ limit,
397
+ emptyMessage = "No team members available.",
398
+ loadingMessage = "Loading team..."
285
399
  }) {
286
- const { data, isLoading, error } = useTeam();
400
+ const { data, isLoading, error } = useTeam(query);
287
401
  if (isLoading) {
288
- return /* @__PURE__ */ jsx5("div", { className: "idk-state", children: "Loading team..." });
402
+ return /* @__PURE__ */ jsx5("div", { className: "idk-state", children: loadingMessage });
403
+ }
404
+ let members = data?.team ? [...data.team] : [];
405
+ if (filter) {
406
+ members = members.filter(filter);
407
+ }
408
+ if (sort) {
409
+ members = members.sort(sort);
289
410
  }
290
- if (error || !data?.team?.length) {
291
- return /* @__PURE__ */ jsx5("div", { className: "idk-state", children: "No team members available." });
411
+ if (typeof limit === "number") {
412
+ members = members.slice(0, Math.max(0, limit));
413
+ }
414
+ if (error || members.length === 0) {
415
+ return /* @__PURE__ */ jsx5("div", { className: "idk-state", children: emptyMessage });
292
416
  }
293
417
  const gridStyle = { gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` };
294
418
  return /* @__PURE__ */ jsx5(
@@ -296,12 +420,15 @@ function TeamGrid({
296
420
  {
297
421
  className: ["idk-team", className].filter(Boolean).join(" "),
298
422
  style: gridStyle,
299
- children: data.team.map((member) => /* @__PURE__ */ jsx5(
423
+ children: members.map((member) => /* @__PURE__ */ jsx5(
300
424
  TeamMember,
301
425
  {
302
426
  member,
303
427
  showRole,
304
- showEmail
428
+ showEmail,
429
+ showBio,
430
+ layout,
431
+ imagePosition
305
432
  },
306
433
  member.id
307
434
  ))
@@ -421,215 +548,674 @@ function ContactForm({
421
548
  }
422
549
 
423
550
  // src/components/BookingWidget.tsx
424
- import { useMemo as useMemo2, useState as useState3 } from "react";
551
+ import { useCallback, useEffect, useMemo as useMemo4, useRef, useState as useState3 } from "react";
425
552
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
426
553
  function BookingWidget({
427
554
  className,
428
555
  showStaffSelection = true,
429
- onSuccess
556
+ onSuccess,
557
+ services,
558
+ servicesQuery,
559
+ teamQuery,
560
+ serviceFilter,
561
+ teamFilter,
562
+ title = "Book an Appointment",
563
+ subtitle,
564
+ autoAdvanceOnSelect = false,
565
+ scrollToStep = true,
566
+ scrollOffset = 96,
567
+ enableServiceSearch = true,
568
+ serviceSearchPlaceholder = "Search services",
569
+ enableCategoryFilter = false,
570
+ categoryLabel = "All categories",
571
+ serviceListMaxHeight,
572
+ serviceLayout = "list",
573
+ serviceColumns = 2,
574
+ servicePageSize = 10,
575
+ showServicePagination,
576
+ staffLayout = "grid",
577
+ staffColumns = 2,
578
+ showAnyStaffOption = true,
579
+ timeLayout = "split"
430
580
  }) {
431
- const { data: servicesData, isLoading: servicesLoading } = useServices();
432
- const { data: teamData } = useTeam();
581
+ const resolvedServicesQuery = useMemo4(
582
+ () => services ? { ...servicesQuery, enabled: false } : servicesQuery,
583
+ [services, servicesQuery]
584
+ );
585
+ const { data: servicesData, isLoading: servicesLoading } = useServices(resolvedServicesQuery);
586
+ const { data: teamData } = useTeam(teamQuery);
433
587
  const createBooking = useCreateBooking();
588
+ const widgetRef = useRef(null);
434
589
  const [step, setStep] = useState3("service");
435
590
  const [selectedService, setSelectedService] = useState3(null);
436
591
  const [selectedStaff, setSelectedStaff] = useState3(null);
437
592
  const [selectedDate, setSelectedDate] = useState3(null);
438
593
  const [selectedTime, setSelectedTime] = useState3(null);
439
594
  const [selectedEndTime, setSelectedEndTime] = useState3(null);
595
+ const [serviceSearch, setServiceSearch] = useState3("");
596
+ const [categoryFilter, setCategoryFilter] = useState3("all");
597
+ const [servicePage, setServicePage] = useState3(1);
598
+ const [bookingError, setBookingError] = useState3(null);
440
599
  const [details, setDetails] = useState3({
441
600
  name: "",
442
601
  email: "",
443
602
  phone: "",
444
603
  notes: ""
445
604
  });
446
- const staffOptions = useMemo2(() => {
605
+ const servicesList = useMemo4(() => {
606
+ const list = services ? [...services] : servicesData?.services ? [...servicesData.services] : [];
607
+ return serviceFilter ? list.filter(serviceFilter) : list;
608
+ }, [services, servicesData, serviceFilter]);
609
+ const categories = useMemo4(() => {
610
+ const set = /* @__PURE__ */ new Set();
611
+ servicesList.forEach((service) => {
612
+ if (service.category) {
613
+ set.add(service.category);
614
+ }
615
+ });
616
+ return Array.from(set).sort();
617
+ }, [servicesList]);
618
+ const filteredServices = useMemo4(() => {
619
+ const search = serviceSearch.trim().toLowerCase();
620
+ return servicesList.filter((service) => {
621
+ const matchesSearch = !search || service.name.toLowerCase().includes(search) || (service.description || "").toLowerCase().includes(search) || (service.category || "").toLowerCase().includes(search);
622
+ const matchesCategory = categoryFilter === "all" ? true : service.category === categoryFilter;
623
+ return matchesSearch && matchesCategory;
624
+ });
625
+ }, [servicesList, serviceSearch, categoryFilter]);
626
+ useEffect(() => {
627
+ setServicePage(1);
628
+ }, [serviceSearch, categoryFilter]);
629
+ const pagedServices = useMemo4(() => {
630
+ const pageSize = Math.max(1, servicePageSize);
631
+ return filteredServices.slice(0, pageSize * servicePage);
632
+ }, [filteredServices, servicePage, servicePageSize]);
633
+ const hasMoreServices = filteredServices.length > pagedServices.length;
634
+ const shouldPaginate = showServicePagination ?? hasMoreServices;
635
+ const staffOptions = useMemo4(() => {
447
636
  if (!selectedService) return [];
448
637
  if (selectedService.assignedStaff && selectedService.assignedStaff.length > 0) {
449
- return selectedService.assignedStaff;
638
+ const assigned = selectedService.assignedStaff;
639
+ return teamFilter ? assigned.filter(teamFilter) : assigned;
450
640
  }
451
- return teamData?.team || [];
452
- }, [selectedService, teamData]);
641
+ const teamList = teamData?.team || [];
642
+ return teamFilter ? teamList.filter(teamFilter) : teamList;
643
+ }, [selectedService, teamData, teamFilter]);
453
644
  const availability = useAvailability({
454
645
  serviceId: selectedService?.id || "",
455
646
  staffId: selectedStaff?.id,
456
647
  days: 7,
457
648
  enabled: Boolean(selectedService)
458
649
  });
650
+ const timeZone = availability.data?.timeZone || "America/Chicago";
651
+ const safeTimeZone = useMemo4(() => {
652
+ if (!timeZone) return void 0;
653
+ try {
654
+ new Intl.DateTimeFormat("en-US", { timeZone }).format(/* @__PURE__ */ new Date());
655
+ return timeZone;
656
+ } catch {
657
+ return void 0;
658
+ }
659
+ }, [timeZone]);
660
+ const timeZoneLabel = useMemo4(() => {
661
+ if (timeZone) return timeZone;
662
+ try {
663
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
664
+ } catch {
665
+ return "Local time";
666
+ }
667
+ }, [timeZone]);
668
+ const dateLabelFormatter = useMemo4(
669
+ () => new Intl.DateTimeFormat("en-US", {
670
+ weekday: "short",
671
+ month: "short",
672
+ day: "numeric"
673
+ }),
674
+ []
675
+ );
676
+ const dateHeadingFormatter = useMemo4(
677
+ () => new Intl.DateTimeFormat("en-US", {
678
+ weekday: "long",
679
+ month: "long",
680
+ day: "numeric"
681
+ }),
682
+ []
683
+ );
684
+ const timeFormatter = useMemo4(
685
+ () => new Intl.DateTimeFormat("en-US", {
686
+ timeZone: safeTimeZone,
687
+ hour: "numeric",
688
+ minute: "2-digit"
689
+ }),
690
+ [safeTimeZone]
691
+ );
459
692
  const requiresStaff = showStaffSelection || selectedService?.requiresStaffSelection || selectedService?.schedulingType === "customer-choice";
693
+ const canSkipStaff = !selectedService?.requiresStaffSelection;
694
+ const totalSteps = requiresStaff ? 4 : 3;
695
+ const stepNumber = (() => {
696
+ if (step === "service") return 1;
697
+ if (step === "staff") return 2;
698
+ if (step === "time") return requiresStaff ? 3 : 2;
699
+ if (step === "details") return requiresStaff ? 4 : 3;
700
+ return totalSteps;
701
+ })();
702
+ const scrollToTop = useCallback(() => {
703
+ if (!scrollToStep || !widgetRef.current) return;
704
+ const top = widgetRef.current.getBoundingClientRect().top + window.scrollY;
705
+ window.scrollTo({
706
+ top: Math.max(0, top - scrollOffset),
707
+ behavior: "smooth"
708
+ });
709
+ }, [scrollToStep, scrollOffset]);
710
+ const resetSelections = () => {
711
+ setSelectedStaff(null);
712
+ setSelectedDate(null);
713
+ setSelectedTime(null);
714
+ setSelectedEndTime(null);
715
+ setBookingError(null);
716
+ };
717
+ useEffect(() => {
718
+ if (step !== "time") return;
719
+ if (!availability.data?.dates?.length) return;
720
+ if (selectedDate) return;
721
+ setSelectedDate(availability.data.dates[0].date);
722
+ }, [step, availability.data, selectedDate]);
723
+ const parseDateOnly = (value) => {
724
+ if (!value) return null;
725
+ const normalized = value.includes("T") ? value.split("T")[0] : value;
726
+ const parsed = /* @__PURE__ */ new Date(`${normalized}T00:00:00`);
727
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
728
+ };
729
+ const parseDateTime = (value, fallbackDate) => {
730
+ if (!value) return null;
731
+ const direct = new Date(value);
732
+ if (!Number.isNaN(direct.getTime())) return direct;
733
+ if (fallbackDate) {
734
+ const normalized = fallbackDate.includes("T") ? fallbackDate.split("T")[0] : fallbackDate;
735
+ const combined = /* @__PURE__ */ new Date(`${normalized}T${value}`);
736
+ if (!Number.isNaN(combined.getTime())) return combined;
737
+ }
738
+ return null;
739
+ };
740
+ const formatDateLabel = (date) => {
741
+ const parsed = parseDateOnly(date);
742
+ if (!parsed) return date;
743
+ try {
744
+ return dateLabelFormatter.format(parsed);
745
+ } catch {
746
+ return date;
747
+ }
748
+ };
749
+ const formatDateHeading = (date) => {
750
+ const parsed = parseDateOnly(date);
751
+ if (!parsed) return date;
752
+ try {
753
+ return dateHeadingFormatter.format(parsed);
754
+ } catch {
755
+ return date;
756
+ }
757
+ };
758
+ const formatTimeLabel = (iso) => {
759
+ const parsed = parseDateTime(iso, selectedDate);
760
+ if (!parsed) return iso;
761
+ try {
762
+ return timeFormatter.format(parsed);
763
+ } catch {
764
+ return parsed.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
765
+ }
766
+ };
767
+ const formatDuration = (minutes) => {
768
+ if (!Number.isFinite(minutes)) return "";
769
+ if (minutes < 60) return `${minutes} min`;
770
+ const hours = Math.floor(minutes / 60);
771
+ const remaining = minutes % 60;
772
+ return remaining === 0 ? `${hours} hr${hours > 1 ? "s" : ""}` : `${hours} hr ${remaining} min`;
773
+ };
774
+ const handleBack = () => {
775
+ if (step === "service") return;
776
+ if (step === "staff") {
777
+ setStep("service");
778
+ } else if (step === "time") {
779
+ if (requiresStaff) {
780
+ setStep("staff");
781
+ } else {
782
+ setStep("service");
783
+ }
784
+ } else if (step === "details") {
785
+ setStep("time");
786
+ } else if (step === "done") {
787
+ setStep("details");
788
+ }
789
+ setBookingError(null);
790
+ scrollToTop();
791
+ };
792
+ const handleServiceContinue = () => {
793
+ if (!selectedService) return;
794
+ if (requiresStaff) {
795
+ setStep("staff");
796
+ } else {
797
+ setStep("time");
798
+ }
799
+ scrollToTop();
800
+ };
801
+ const handleStaffContinue = () => {
802
+ if (!canSkipStaff && !selectedStaff) return;
803
+ setStep("time");
804
+ scrollToTop();
805
+ };
806
+ const handleTimeContinue = () => {
807
+ if (!selectedTime || !selectedEndTime) return;
808
+ setStep("details");
809
+ scrollToTop();
810
+ };
460
811
  const handleSubmit = async (event) => {
461
812
  event.preventDefault();
462
813
  if (!selectedService || !selectedDate || !selectedTime || !selectedEndTime) return;
463
- const start = /* @__PURE__ */ new Date(`${selectedDate}T${selectedTime}:00`);
464
- const end = /* @__PURE__ */ new Date(`${selectedDate}T${selectedEndTime}:00`);
465
- const result = await createBooking.mutateAsync({
466
- serviceId: selectedService.id,
467
- staffId: selectedStaff?.id,
468
- startTime: start.toISOString(),
469
- endTime: end.toISOString(),
470
- customerName: details.name,
471
- customerEmail: details.email,
472
- customerPhone: details.phone || void 0,
473
- customerNotes: details.notes || void 0
474
- });
475
- setStep("done");
476
- onSuccess?.(result?.booking);
814
+ const start = parseDateTime(selectedTime, selectedDate);
815
+ const end = parseDateTime(selectedEndTime, selectedDate);
816
+ if (!start || !end) {
817
+ setBookingError("Please select a valid time slot.");
818
+ return;
819
+ }
820
+ try {
821
+ setBookingError(null);
822
+ const result = await createBooking.mutateAsync({
823
+ serviceId: selectedService.id,
824
+ staffId: selectedStaff?.id,
825
+ startTime: start.toISOString(),
826
+ endTime: end.toISOString(),
827
+ customerName: details.name,
828
+ customerEmail: details.email,
829
+ customerPhone: details.phone || void 0,
830
+ customerNotes: details.notes || void 0
831
+ });
832
+ setStep("done");
833
+ onSuccess?.(result?.booking);
834
+ scrollToTop();
835
+ } catch (error) {
836
+ setBookingError(
837
+ error instanceof Error ? error.message : "Unable to complete booking. Please try again."
838
+ );
839
+ }
477
840
  };
478
- if (servicesLoading) {
841
+ const isServicesLoading = services ? false : servicesLoading;
842
+ if (isServicesLoading) {
479
843
  return /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Loading booking options..." });
480
844
  }
481
- if (!servicesData?.services?.length) {
845
+ if (!servicesList.length) {
482
846
  return /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No services available." });
483
847
  }
484
- return /* @__PURE__ */ jsxs4("div", { className: ["idk-booking", className].filter(Boolean).join(" "), children: [
485
- step === "service" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
486
- /* @__PURE__ */ jsx7("h3", { children: "Select a service" }),
487
- /* @__PURE__ */ jsx7("div", { className: "idk-services idk-services--list", children: servicesData.services.map((service) => /* @__PURE__ */ jsxs4(
488
- "button",
489
- {
490
- type: "button",
491
- className: "idk-card idk-card--clickable",
492
- onClick: () => {
493
- setSelectedService(service);
494
- if (requiresStaff) {
495
- setStep("staff");
496
- } else {
497
- setStep("time");
848
+ return /* @__PURE__ */ jsxs4(
849
+ "div",
850
+ {
851
+ ref: widgetRef,
852
+ className: ["idk-booking", className].filter(Boolean).join(" "),
853
+ children: [
854
+ /* @__PURE__ */ jsxs4("div", { className: "idk-booking__header", children: [
855
+ /* @__PURE__ */ jsxs4("div", { className: "idk-booking__title-wrap", children: [
856
+ /* @__PURE__ */ jsx7("h3", { className: "idk-booking__title", children: title }),
857
+ subtitle ? /* @__PURE__ */ jsx7("p", { className: "idk-booking__subtitle", children: subtitle }) : null
858
+ ] }),
859
+ step !== "service" && step !== "done" ? /* @__PURE__ */ jsx7("button", { type: "button", className: "idk-link", onClick: handleBack, children: "Back" }) : null
860
+ ] }),
861
+ step !== "done" ? /* @__PURE__ */ jsxs4("div", { className: "idk-booking__progress", children: [
862
+ /* @__PURE__ */ jsxs4("span", { children: [
863
+ "Step ",
864
+ stepNumber,
865
+ " of ",
866
+ totalSteps
867
+ ] }),
868
+ /* @__PURE__ */ jsx7("div", { className: "idk-booking__bar", children: /* @__PURE__ */ jsx7(
869
+ "div",
870
+ {
871
+ className: "idk-booking__bar-fill",
872
+ style: { width: `${stepNumber / totalSteps * 100}%` }
498
873
  }
499
- },
500
- children: [
501
- /* @__PURE__ */ jsxs4("div", { className: "idk-card__header", children: [
502
- /* @__PURE__ */ jsx7("span", { className: "idk-card__title", children: service.name }),
503
- typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ jsxs4("span", { className: "idk-card__price", children: [
504
- "$",
505
- (service.price / 100).toFixed(2)
506
- ] }) : null
507
- ] }),
508
- /* @__PURE__ */ jsxs4("div", { className: "idk-card__meta", children: [
509
- service.duration,
510
- " min"
874
+ ) })
875
+ ] }) : null,
876
+ step === "service" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
877
+ /* @__PURE__ */ jsx7("h4", { children: "Select a service" }),
878
+ enableServiceSearch || enableCategoryFilter ? /* @__PURE__ */ jsxs4("div", { className: "idk-booking__filters", children: [
879
+ enableServiceSearch ? /* @__PURE__ */ jsx7(
880
+ "input",
881
+ {
882
+ type: "search",
883
+ value: serviceSearch,
884
+ onChange: (event) => setServiceSearch(event.target.value),
885
+ placeholder: serviceSearchPlaceholder
886
+ }
887
+ ) : null,
888
+ enableCategoryFilter && categories.length > 0 ? /* @__PURE__ */ jsxs4(
889
+ "select",
890
+ {
891
+ value: categoryFilter,
892
+ onChange: (event) => setCategoryFilter(event.target.value),
893
+ children: [
894
+ /* @__PURE__ */ jsx7("option", { value: "all", children: categoryLabel }),
895
+ categories.map((category) => /* @__PURE__ */ jsx7("option", { value: category, children: category }, category))
896
+ ]
897
+ }
898
+ ) : null
899
+ ] }) : null,
900
+ filteredServices.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No services match your filters." }) : /* @__PURE__ */ jsx7(
901
+ "div",
902
+ {
903
+ className: [
904
+ "idk-services",
905
+ serviceLayout === "grid" ? "idk-services--grid" : "idk-services--list"
906
+ ].join(" "),
907
+ style: {
908
+ ...serviceLayout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, serviceColumns)}, minmax(0, 1fr))` } : void 0,
909
+ ...serviceListMaxHeight ? { maxHeight: serviceListMaxHeight, overflow: "auto" } : void 0
910
+ },
911
+ children: pagedServices.map((service) => {
912
+ const isSelected = selectedService?.id === service.id;
913
+ return /* @__PURE__ */ jsxs4(
914
+ "button",
915
+ {
916
+ type: "button",
917
+ className: ["idk-card", "idk-card--clickable", isSelected ? "is-active" : ""].join(" "),
918
+ onClick: () => {
919
+ setSelectedService(service);
920
+ resetSelections();
921
+ if (autoAdvanceOnSelect) {
922
+ if (requiresStaff) {
923
+ setStep("staff");
924
+ } else {
925
+ setStep("time");
926
+ }
927
+ scrollToTop();
928
+ }
929
+ },
930
+ children: [
931
+ /* @__PURE__ */ jsxs4("div", { className: "idk-card__header", children: [
932
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__title", children: service.name }),
933
+ /* @__PURE__ */ jsxs4("span", { className: "idk-card__aside", children: [
934
+ isSelected ? /* @__PURE__ */ jsx7("span", { className: "idk-card__check", children: "\u2713" }) : null,
935
+ typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ jsxs4("span", { className: "idk-card__price", children: [
936
+ "$",
937
+ (service.price / 100).toFixed(2)
938
+ ] }) : null
939
+ ] })
940
+ ] }),
941
+ service.description ? /* @__PURE__ */ jsx7("div", { className: "idk-card__description", children: service.description }) : null,
942
+ /* @__PURE__ */ jsxs4("div", { className: "idk-card__meta", children: [
943
+ /* @__PURE__ */ jsx7("span", { children: formatDuration(service.duration) }),
944
+ service.category ? /* @__PURE__ */ jsx7("span", { className: "idk-pill", children: service.category }) : null
945
+ ] })
946
+ ]
947
+ },
948
+ service.id
949
+ );
950
+ })
951
+ }
952
+ ),
953
+ /* @__PURE__ */ jsxs4("div", { className: "idk-booking__actions", children: [
954
+ shouldPaginate ? /* @__PURE__ */ jsx7(
955
+ "button",
956
+ {
957
+ type: "button",
958
+ className: "idk-button idk-button--ghost",
959
+ onClick: () => setServicePage((prev) => prev + 1),
960
+ disabled: !hasMoreServices,
961
+ children: hasMoreServices ? "Show more services" : "All services shown"
962
+ }
963
+ ) : null,
964
+ selectedService && !autoAdvanceOnSelect ? /* @__PURE__ */ jsx7("button", { type: "button", className: "idk-button", onClick: handleServiceContinue, children: "Continue" }) : null
965
+ ] })
966
+ ] }),
967
+ step === "staff" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
968
+ /* @__PURE__ */ jsx7("h4", { children: "Select a team member" }),
969
+ staffOptions.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No team members available." }) : /* @__PURE__ */ jsxs4(
970
+ "div",
971
+ {
972
+ className: [
973
+ "idk-team",
974
+ staffLayout === "grid" ? "idk-team--grid" : "idk-team--list"
975
+ ].join(" "),
976
+ style: staffLayout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, staffColumns)}, minmax(0, 1fr))` } : void 0,
977
+ children: [
978
+ showAnyStaffOption && canSkipStaff ? /* @__PURE__ */ jsxs4(
979
+ "button",
980
+ {
981
+ type: "button",
982
+ className: ["idk-card", "idk-card--clickable", !selectedStaff ? "is-active" : ""].join(" "),
983
+ onClick: () => {
984
+ setSelectedStaff(null);
985
+ setSelectedDate(null);
986
+ setSelectedTime(null);
987
+ setSelectedEndTime(null);
988
+ if (autoAdvanceOnSelect) {
989
+ setStep("time");
990
+ scrollToTop();
991
+ }
992
+ },
993
+ children: [
994
+ /* @__PURE__ */ jsxs4("div", { className: "idk-card__header", children: [
995
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__title", children: "Any Available" }),
996
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__badge", children: "Recommended" })
997
+ ] }),
998
+ /* @__PURE__ */ jsx7("p", { className: "idk-card__description", children: "We'll match you with the first available stylist." })
999
+ ]
1000
+ },
1001
+ "any-staff"
1002
+ ) : null,
1003
+ staffOptions.map((staff) => /* @__PURE__ */ jsx7(
1004
+ "button",
1005
+ {
1006
+ type: "button",
1007
+ className: ["idk-card", "idk-card--clickable", selectedStaff?.id === staff.id ? "is-active" : ""].join(" "),
1008
+ onClick: () => {
1009
+ setSelectedStaff(staff);
1010
+ setSelectedDate(null);
1011
+ setSelectedTime(null);
1012
+ setSelectedEndTime(null);
1013
+ setBookingError(null);
1014
+ if (autoAdvanceOnSelect) {
1015
+ setStep("time");
1016
+ scrollToTop();
1017
+ }
1018
+ },
1019
+ children: /* @__PURE__ */ jsxs4("div", { className: "idk-team__card", children: [
1020
+ /* @__PURE__ */ jsx7("div", { className: "idk-team__avatar", children: staff.photo?.url ? /* @__PURE__ */ jsx7("img", { src: staff.photo.url, alt: staff.name }) : /* @__PURE__ */ jsx7("span", { children: staff.name.split(" ").map((part) => part[0]).slice(0, 2).join("").toUpperCase() }) }),
1021
+ /* @__PURE__ */ jsxs4("div", { children: [
1022
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__title", children: staff.name }),
1023
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__meta", children: staff.role || "Staff" }),
1024
+ staff.bio ? /* @__PURE__ */ jsx7("p", { className: "idk-card__description", children: staff.bio }) : null
1025
+ ] })
1026
+ ] })
1027
+ },
1028
+ staff.id
1029
+ ))
1030
+ ]
1031
+ }
1032
+ ),
1033
+ /* @__PURE__ */ jsxs4("div", { className: "idk-booking__actions", children: [
1034
+ canSkipStaff ? /* @__PURE__ */ jsx7(
1035
+ "button",
1036
+ {
1037
+ type: "button",
1038
+ className: "idk-link",
1039
+ onClick: () => {
1040
+ setSelectedStaff(null);
1041
+ setStep("time");
1042
+ scrollToTop();
1043
+ },
1044
+ children: "Continue without selecting"
1045
+ }
1046
+ ) : null,
1047
+ !autoAdvanceOnSelect ? /* @__PURE__ */ jsx7(
1048
+ "button",
1049
+ {
1050
+ type: "button",
1051
+ className: "idk-button",
1052
+ onClick: handleStaffContinue,
1053
+ disabled: !canSkipStaff && !selectedStaff,
1054
+ children: "Continue"
1055
+ }
1056
+ ) : null
1057
+ ] })
1058
+ ] }),
1059
+ step === "time" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
1060
+ /* @__PURE__ */ jsx7("h4", { children: "Select a time" }),
1061
+ availability.isLoading ? /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Loading availability..." }) : availability.data ? /* @__PURE__ */ jsxs4("div", { className: ["idk-availability", timeLayout === "split" ? "idk-availability--split" : ""].join(" "), children: [
1062
+ /* @__PURE__ */ jsx7("div", { className: "idk-availability__dates", children: availability.data.dates.map((entry) => /* @__PURE__ */ jsx7(
1063
+ "button",
1064
+ {
1065
+ type: "button",
1066
+ className: entry.date === selectedDate ? "is-active" : void 0,
1067
+ onClick: () => {
1068
+ setSelectedDate(entry.date);
1069
+ setSelectedTime(null);
1070
+ setSelectedEndTime(null);
1071
+ setBookingError(null);
1072
+ },
1073
+ children: /* @__PURE__ */ jsx7("span", { className: "idk-date-chip__day", children: formatDateLabel(entry.date) })
1074
+ },
1075
+ entry.date
1076
+ )) }),
1077
+ /* @__PURE__ */ jsxs4("div", { className: "idk-availability__panel", children: [
1078
+ /* @__PURE__ */ jsx7("div", { className: "idk-availability__panel-header", children: /* @__PURE__ */ jsxs4("div", { children: [
1079
+ /* @__PURE__ */ jsx7("div", { className: "idk-availability__panel-title", children: selectedDate ? formatDateHeading(selectedDate) : availability.data.dates[0]?.date ? formatDateHeading(availability.data.dates[0].date) : "Select a date" }),
1080
+ /* @__PURE__ */ jsx7("div", { className: "idk-availability__panel-subtitle", children: timeZoneLabel })
1081
+ ] }) }),
1082
+ /* @__PURE__ */ jsx7("div", { className: "idk-availability__slots", children: (() => {
1083
+ const activeEntry = availability.data.dates.find((entry) => entry.date === selectedDate) || availability.data.dates[0];
1084
+ if (!activeEntry) return /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Select a date to see times." });
1085
+ const slots = (activeEntry.slots || []).filter((slot) => slot.available);
1086
+ if (slots.length === 0) {
1087
+ return /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No available times for this date." });
1088
+ }
1089
+ return slots.map((slot) => /* @__PURE__ */ jsx7(
1090
+ "button",
1091
+ {
1092
+ type: "button",
1093
+ className: slot.time === selectedTime ? "is-active" : void 0,
1094
+ onClick: () => {
1095
+ const date = selectedDate || activeEntry.date;
1096
+ setSelectedDate(date || null);
1097
+ setSelectedTime(slot.time);
1098
+ setSelectedEndTime(slot.endTime);
1099
+ setBookingError(null);
1100
+ if (autoAdvanceOnSelect) {
1101
+ setStep("details");
1102
+ scrollToTop();
1103
+ }
1104
+ },
1105
+ children: formatTimeLabel(slot.time)
1106
+ },
1107
+ `${slot.time}-${slot.endTime}`
1108
+ ));
1109
+ })() })
511
1110
  ] })
512
- ]
513
- },
514
- service.id
515
- )) })
516
- ] }),
517
- step === "staff" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
518
- /* @__PURE__ */ jsx7("h3", { children: "Select a team member" }),
519
- /* @__PURE__ */ jsx7("div", { className: "idk-team", children: staffOptions.map((staff) => /* @__PURE__ */ jsxs4(
520
- "button",
521
- {
522
- type: "button",
523
- className: "idk-card idk-card--clickable",
524
- onClick: () => {
525
- setSelectedStaff(staff);
526
- setStep("time");
527
- },
528
- children: [
529
- /* @__PURE__ */ jsx7("span", { className: "idk-card__title", children: staff.name }),
530
- /* @__PURE__ */ jsx7("span", { className: "idk-card__meta", children: staff.role || "Staff" })
531
- ]
532
- },
533
- staff.id
534
- )) }),
535
- /* @__PURE__ */ jsx7(
536
- "button",
537
- {
538
- type: "button",
539
- className: "idk-link",
540
- onClick: () => {
541
- setSelectedStaff(null);
542
- setStep("time");
543
- },
544
- children: "Continue without selecting"
545
- }
546
- )
547
- ] }),
548
- step === "time" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
549
- /* @__PURE__ */ jsx7("h3", { children: "Select a time" }),
550
- availability.isLoading ? /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Loading availability..." }) : availability.data ? /* @__PURE__ */ jsxs4("div", { className: "idk-availability", children: [
551
- /* @__PURE__ */ jsx7("div", { className: "idk-availability__dates", children: availability.data.dates.map((entry) => /* @__PURE__ */ jsx7(
552
- "button",
553
- {
554
- type: "button",
555
- className: entry.date === selectedDate ? "is-active" : void 0,
556
- onClick: () => {
557
- setSelectedDate(entry.date);
558
- setSelectedTime(null);
559
- setSelectedEndTime(null);
560
- },
561
- children: entry.date
562
- },
563
- entry.date
564
- )) }),
565
- /* @__PURE__ */ jsx7("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__ */ jsx7(
566
- "button",
567
- {
568
- type: "button",
569
- className: slot.time === selectedTime ? "is-active" : void 0,
570
- onClick: () => {
571
- const date = selectedDate || availability.data?.dates[0]?.date;
572
- setSelectedDate(date || null);
573
- setSelectedTime(slot.time);
574
- setSelectedEndTime(slot.endTime);
575
- setStep("details");
576
- },
577
- children: slot.time
578
- },
579
- `${slot.time}-${slot.endTime}`
580
- )) })
581
- ] }) : /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No availability found." })
582
- ] }),
583
- step === "details" && /* @__PURE__ */ jsxs4("form", { className: "idk-form", onSubmit: handleSubmit, children: [
584
- /* @__PURE__ */ jsx7("h3", { children: "Your details" }),
585
- /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
586
- /* @__PURE__ */ jsx7("span", { children: "Name" }),
587
- /* @__PURE__ */ jsx7(
588
- "input",
589
- {
590
- value: details.name,
591
- onChange: (event) => setDetails((prev) => ({ ...prev, name: event.target.value })),
592
- required: true
593
- }
594
- )
595
- ] }),
596
- /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
597
- /* @__PURE__ */ jsx7("span", { children: "Email" }),
598
- /* @__PURE__ */ jsx7(
599
- "input",
600
- {
601
- type: "email",
602
- value: details.email,
603
- onChange: (event) => setDetails((prev) => ({ ...prev, email: event.target.value })),
604
- required: true
605
- }
606
- )
607
- ] }),
608
- /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
609
- /* @__PURE__ */ jsx7("span", { children: "Phone" }),
610
- /* @__PURE__ */ jsx7(
611
- "input",
612
- {
613
- value: details.phone,
614
- onChange: (event) => setDetails((prev) => ({ ...prev, phone: event.target.value }))
615
- }
616
- )
617
- ] }),
618
- /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
619
- /* @__PURE__ */ jsx7("span", { children: "Notes" }),
620
- /* @__PURE__ */ jsx7(
621
- "textarea",
622
- {
623
- rows: 4,
624
- value: details.notes,
625
- onChange: (event) => setDetails((prev) => ({ ...prev, notes: event.target.value }))
626
- }
627
- )
628
- ] }),
629
- /* @__PURE__ */ jsx7("button", { type: "submit", className: "idk-button", disabled: createBooking.isPending, children: createBooking.isPending ? "Booking..." : "Confirm booking" })
630
- ] }),
631
- step === "done" && /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Booking confirmed. You'll receive a confirmation email shortly." })
632
- ] });
1111
+ ] }) : /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No availability found." }),
1112
+ !autoAdvanceOnSelect ? /* @__PURE__ */ jsx7("div", { className: "idk-booking__actions", children: /* @__PURE__ */ jsx7(
1113
+ "button",
1114
+ {
1115
+ type: "button",
1116
+ className: "idk-button",
1117
+ onClick: handleTimeContinue,
1118
+ disabled: !selectedTime || !selectedEndTime,
1119
+ children: "Continue"
1120
+ }
1121
+ ) }) : null
1122
+ ] }),
1123
+ step === "details" && /* @__PURE__ */ jsxs4("form", { className: "idk-form", onSubmit: handleSubmit, children: [
1124
+ /* @__PURE__ */ jsx7("h4", { children: "Your details" }),
1125
+ bookingError ? /* @__PURE__ */ jsx7("div", { className: "idk-booking__error", children: bookingError }) : null,
1126
+ selectedService && selectedTime ? /* @__PURE__ */ jsxs4("div", { className: "idk-booking__summary", children: [
1127
+ /* @__PURE__ */ jsxs4("div", { children: [
1128
+ /* @__PURE__ */ jsx7("span", { className: "idk-booking__summary-label", children: "Service" }),
1129
+ /* @__PURE__ */ jsx7("p", { children: selectedService.name })
1130
+ ] }),
1131
+ selectedStaff ? /* @__PURE__ */ jsxs4("div", { children: [
1132
+ /* @__PURE__ */ jsx7("span", { className: "idk-booking__summary-label", children: "Staff" }),
1133
+ /* @__PURE__ */ jsx7("p", { children: selectedStaff.name })
1134
+ ] }) : null,
1135
+ /* @__PURE__ */ jsxs4("div", { children: [
1136
+ /* @__PURE__ */ jsx7("span", { className: "idk-booking__summary-label", children: "When" }),
1137
+ /* @__PURE__ */ jsxs4("p", { children: [
1138
+ formatDateHeading(selectedDate || ""),
1139
+ " \xB7 ",
1140
+ formatTimeLabel(selectedTime)
1141
+ ] })
1142
+ ] }),
1143
+ selectedService.price ? /* @__PURE__ */ jsxs4("div", { children: [
1144
+ /* @__PURE__ */ jsx7("span", { className: "idk-booking__summary-label", children: "Price" }),
1145
+ /* @__PURE__ */ jsxs4("p", { children: [
1146
+ "$",
1147
+ (selectedService.price / 100).toFixed(2)
1148
+ ] })
1149
+ ] }) : null
1150
+ ] }) : null,
1151
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
1152
+ /* @__PURE__ */ jsx7("span", { children: "Name" }),
1153
+ /* @__PURE__ */ jsx7(
1154
+ "input",
1155
+ {
1156
+ value: details.name,
1157
+ onChange: (event) => setDetails((prev) => ({ ...prev, name: event.target.value })),
1158
+ required: true
1159
+ }
1160
+ )
1161
+ ] }),
1162
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
1163
+ /* @__PURE__ */ jsx7("span", { children: "Email" }),
1164
+ /* @__PURE__ */ jsx7(
1165
+ "input",
1166
+ {
1167
+ type: "email",
1168
+ value: details.email,
1169
+ onChange: (event) => setDetails((prev) => ({ ...prev, email: event.target.value })),
1170
+ required: true
1171
+ }
1172
+ )
1173
+ ] }),
1174
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
1175
+ /* @__PURE__ */ jsx7("span", { children: "Phone" }),
1176
+ /* @__PURE__ */ jsx7(
1177
+ "input",
1178
+ {
1179
+ value: details.phone,
1180
+ onChange: (event) => setDetails((prev) => ({ ...prev, phone: event.target.value }))
1181
+ }
1182
+ )
1183
+ ] }),
1184
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
1185
+ /* @__PURE__ */ jsx7("span", { children: "Notes" }),
1186
+ /* @__PURE__ */ jsx7(
1187
+ "textarea",
1188
+ {
1189
+ rows: 4,
1190
+ value: details.notes,
1191
+ onChange: (event) => setDetails((prev) => ({ ...prev, notes: event.target.value }))
1192
+ }
1193
+ )
1194
+ ] }),
1195
+ /* @__PURE__ */ jsx7("button", { type: "submit", className: "idk-button", disabled: createBooking.isPending, children: createBooking.isPending ? "Booking..." : "Confirm booking" })
1196
+ ] }),
1197
+ step === "done" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__done", children: [
1198
+ /* @__PURE__ */ jsx7("div", { className: "idk-booking__done-title", children: "Booking confirmed!" }),
1199
+ /* @__PURE__ */ jsx7("p", { className: "idk-booking__done-text", children: "We'll send a confirmation email shortly with your appointment details." }),
1200
+ /* @__PURE__ */ jsx7(
1201
+ "button",
1202
+ {
1203
+ type: "button",
1204
+ className: "idk-link",
1205
+ onClick: () => {
1206
+ setStep("service");
1207
+ setSelectedService(null);
1208
+ resetSelections();
1209
+ setDetails({ name: "", email: "", phone: "", notes: "" });
1210
+ scrollToTop();
1211
+ },
1212
+ children: "Book another appointment"
1213
+ }
1214
+ )
1215
+ ] })
1216
+ ]
1217
+ }
1218
+ );
633
1219
  }
634
1220
 
635
1221
  // src/components/AvailabilityPicker.tsx