@idkwebsites/components 0.1.0

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 ADDED
@@ -0,0 +1,806 @@
1
+ // src/core/PlatformProvider.tsx
2
+ import { useState } from "react";
3
+ import {
4
+ QueryClient,
5
+ QueryClientProvider,
6
+ HydrationBoundary
7
+ } from "@tanstack/react-query";
8
+
9
+ // src/core/context.ts
10
+ import { createContext, useContext } from "react";
11
+ var PlatformContext = createContext(null);
12
+ function usePlatformConfig() {
13
+ const context = useContext(PlatformContext);
14
+ if (!context) {
15
+ throw new Error("PlatformProvider is missing in the component tree.");
16
+ }
17
+ return context;
18
+ }
19
+
20
+ // src/core/PlatformProvider.tsx
21
+ import { jsx } from "react/jsx-runtime";
22
+ var DEFAULT_API_URL = process.env.NEXT_PUBLIC_IDK_API_URL || process.env.NEXT_PUBLIC_PLATFORM_API_URL || "https://app.idkwebsites.com/api/v1";
23
+ function PlatformProvider({
24
+ apiKey,
25
+ apiUrl = DEFAULT_API_URL,
26
+ children,
27
+ dehydratedState,
28
+ queryOptions,
29
+ queryClient
30
+ }) {
31
+ const [client] = useState(
32
+ () => queryClient || new QueryClient({
33
+ defaultOptions: {
34
+ queries: {
35
+ staleTime: 3e4,
36
+ retry: 1,
37
+ ...queryOptions?.queries
38
+ },
39
+ mutations: {
40
+ ...queryOptions?.mutations
41
+ }
42
+ }
43
+ })
44
+ );
45
+ return /* @__PURE__ */ jsx(PlatformContext.Provider, { value: { apiKey, apiUrl }, children: /* @__PURE__ */ jsx(QueryClientProvider, { client, children: /* @__PURE__ */ jsx(HydrationBoundary, { state: dehydratedState, children }) }) });
46
+ }
47
+
48
+ // src/core/hooks/useTenant.ts
49
+ import { useQuery } from "@tanstack/react-query";
50
+
51
+ // src/core/api-client.ts
52
+ var ApiError = class extends Error {
53
+ status;
54
+ code;
55
+ constructor(message, status, code) {
56
+ super(message);
57
+ this.name = "ApiError";
58
+ this.status = status;
59
+ this.code = code;
60
+ }
61
+ };
62
+ function normalizeBaseUrl(apiUrl) {
63
+ return apiUrl.replace(/\/+$/, "");
64
+ }
65
+ async function apiRequest(config, path, options = {}) {
66
+ const url = `${normalizeBaseUrl(config.apiUrl)}${path.startsWith("/") ? path : `/${path}`}`;
67
+ const headers = new Headers(options.headers);
68
+ headers.set("X-API-Key", config.apiKey);
69
+ if (!headers.has("Content-Type") && options.body) {
70
+ headers.set("Content-Type", "application/json");
71
+ }
72
+ const response = await fetch(url, {
73
+ ...options,
74
+ headers
75
+ });
76
+ const payload = await response.json().catch(() => null);
77
+ if (!response.ok) {
78
+ const errorCode = payload?.error?.code || "REQUEST_FAILED";
79
+ const errorMessage = payload?.error?.message || response.statusText;
80
+ throw new ApiError(errorMessage, response.status, errorCode);
81
+ }
82
+ if (payload?.error) {
83
+ throw new ApiError(payload.error.message || "Request failed", payload.error.status || 400, payload.error.code || "REQUEST_FAILED");
84
+ }
85
+ return payload?.data;
86
+ }
87
+
88
+ // src/core/hooks/useTenant.ts
89
+ function useTenant() {
90
+ const config = usePlatformConfig();
91
+ return useQuery({
92
+ queryKey: ["idk", "tenant"],
93
+ queryFn: () => apiRequest(config, "/tenant")
94
+ });
95
+ }
96
+
97
+ // src/core/hooks/useServices.ts
98
+ import { useQuery as useQuery2 } from "@tanstack/react-query";
99
+ function useServices() {
100
+ const config = usePlatformConfig();
101
+ return useQuery2({
102
+ queryKey: ["idk", "services"],
103
+ queryFn: () => apiRequest(config, "/services")
104
+ });
105
+ }
106
+
107
+ // src/core/hooks/useTeam.ts
108
+ import { useQuery as useQuery3 } from "@tanstack/react-query";
109
+ function useTeam() {
110
+ const config = usePlatformConfig();
111
+ return useQuery3({
112
+ queryKey: ["idk", "team"],
113
+ queryFn: () => apiRequest(config, "/team")
114
+ });
115
+ }
116
+
117
+ // src/core/hooks/useAvailability.ts
118
+ import { useMemo } from "react";
119
+ import { useQuery as useQuery4 } from "@tanstack/react-query";
120
+ function buildQuery(params) {
121
+ const searchParams = new URLSearchParams();
122
+ searchParams.set("serviceId", params.serviceId);
123
+ if (params.staffId) searchParams.set("staffId", params.staffId);
124
+ if (params.startDate) searchParams.set("startDate", params.startDate);
125
+ if (params.endDate) searchParams.set("endDate", params.endDate);
126
+ if (params.date) searchParams.set("date", params.date);
127
+ if (params.days) searchParams.set("days", String(params.days));
128
+ return searchParams.toString();
129
+ }
130
+ function useAvailability(params) {
131
+ const config = usePlatformConfig();
132
+ const queryString = useMemo(
133
+ () => buildQuery(params),
134
+ [params.serviceId, params.staffId, params.startDate, params.endDate, params.date, params.days]
135
+ );
136
+ const enabled = params.enabled ?? Boolean(params.serviceId);
137
+ return useQuery4({
138
+ queryKey: ["idk", "availability", queryString],
139
+ queryFn: () => apiRequest(config, `/availability?${queryString}`),
140
+ enabled
141
+ });
142
+ }
143
+
144
+ // src/core/hooks/useBooking.ts
145
+ import { useMutation, useQuery as useQuery5 } from "@tanstack/react-query";
146
+ function useCreateBooking() {
147
+ const config = usePlatformConfig();
148
+ return useMutation({
149
+ mutationFn: (input) => apiRequest(config, "/bookings", {
150
+ method: "POST",
151
+ body: JSON.stringify(input)
152
+ })
153
+ });
154
+ }
155
+ function useCancelBooking() {
156
+ const config = usePlatformConfig();
157
+ return useMutation({
158
+ mutationFn: (input) => apiRequest(config, `/bookings/${input.uid}/cancel`, {
159
+ method: "POST",
160
+ body: JSON.stringify({
161
+ reason: input.reason,
162
+ cancelledBy: input.cancelledBy || "customer"
163
+ })
164
+ })
165
+ });
166
+ }
167
+ function useBookingLookup(uid, enabled = true) {
168
+ const config = usePlatformConfig();
169
+ return useQuery5({
170
+ queryKey: ["idk", "booking", uid],
171
+ queryFn: () => apiRequest(config, `/bookings/${uid}`),
172
+ enabled: Boolean(uid) && enabled
173
+ });
174
+ }
175
+
176
+ // src/components/ServiceCard.tsx
177
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
178
+ function ServiceCard({
179
+ service,
180
+ className,
181
+ showDescription = true,
182
+ showPrice = true,
183
+ onSelect
184
+ }) {
185
+ const handleClick = () => {
186
+ if (onSelect) onSelect(service);
187
+ };
188
+ return /* @__PURE__ */ jsxs(
189
+ "div",
190
+ {
191
+ className: ["idk-card", onSelect ? "idk-card--clickable" : "", className].filter(Boolean).join(" "),
192
+ onClick: onSelect ? handleClick : void 0,
193
+ role: onSelect ? "button" : void 0,
194
+ tabIndex: onSelect ? 0 : void 0,
195
+ children: [
196
+ /* @__PURE__ */ jsxs("div", { className: "idk-card__header", children: [
197
+ /* @__PURE__ */ jsx2("h3", { className: "idk-card__title", children: service.name }),
198
+ showPrice && typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ jsxs("span", { className: "idk-card__price", children: [
199
+ "$",
200
+ (service.price / 100).toFixed(2)
201
+ ] }) : null
202
+ ] }),
203
+ showDescription && service.description ? /* @__PURE__ */ jsx2("p", { className: "idk-card__description", children: service.description }) : null,
204
+ /* @__PURE__ */ jsxs("div", { className: "idk-card__meta", children: [
205
+ /* @__PURE__ */ jsxs("span", { children: [
206
+ service.duration,
207
+ " min"
208
+ ] }),
209
+ service.category ? /* @__PURE__ */ jsx2("span", { children: service.category }) : null
210
+ ] })
211
+ ]
212
+ }
213
+ );
214
+ }
215
+
216
+ // src/components/ServicesList.tsx
217
+ import { jsx as jsx3 } from "react/jsx-runtime";
218
+ function ServicesList({
219
+ layout = "grid",
220
+ columns = 3,
221
+ className,
222
+ showDescription,
223
+ showPrice,
224
+ onSelect
225
+ }) {
226
+ const { data, isLoading, error } = useServices();
227
+ if (isLoading) {
228
+ return /* @__PURE__ */ jsx3("div", { className: "idk-state", children: "Loading services..." });
229
+ }
230
+ if (error || !data?.services?.length) {
231
+ return /* @__PURE__ */ jsx3("div", { className: "idk-state", children: "No services available." });
232
+ }
233
+ const gridStyle = layout === "grid" ? { gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` } : void 0;
234
+ return /* @__PURE__ */ jsx3(
235
+ "div",
236
+ {
237
+ className: [
238
+ "idk-services",
239
+ layout === "grid" ? "idk-services--grid" : "idk-services--list",
240
+ className
241
+ ].filter(Boolean).join(" "),
242
+ style: gridStyle,
243
+ children: data.services.map((service) => /* @__PURE__ */ jsx3(
244
+ ServiceCard,
245
+ {
246
+ service,
247
+ showDescription,
248
+ showPrice,
249
+ onSelect
250
+ },
251
+ service.id
252
+ ))
253
+ }
254
+ );
255
+ }
256
+
257
+ // src/components/TeamMember.tsx
258
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
259
+ function TeamMember({
260
+ member,
261
+ className,
262
+ showRole = true,
263
+ showEmail = false
264
+ }) {
265
+ const initials = member.name?.split(" ").map((part) => part[0]).slice(0, 2).join("").toUpperCase();
266
+ return /* @__PURE__ */ jsxs2("div", { className: ["idk-card", className].filter(Boolean).join(" "), children: [
267
+ /* @__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 }) }),
268
+ /* @__PURE__ */ jsxs2("div", { className: "idk-team__info", children: [
269
+ /* @__PURE__ */ jsx4("h3", { className: "idk-card__title", children: member.name }),
270
+ showRole ? /* @__PURE__ */ jsx4("p", { className: "idk-card__description", children: member.role || "Staff" }) : null,
271
+ showEmail ? /* @__PURE__ */ jsx4("p", { className: "idk-card__meta", children: member.email }) : null
272
+ ] })
273
+ ] });
274
+ }
275
+
276
+ // src/components/TeamGrid.tsx
277
+ import { jsx as jsx5 } from "react/jsx-runtime";
278
+ function TeamGrid({
279
+ columns = 3,
280
+ className,
281
+ showRole,
282
+ showEmail
283
+ }) {
284
+ const { data, isLoading, error } = useTeam();
285
+ if (isLoading) {
286
+ return /* @__PURE__ */ jsx5("div", { className: "idk-state", children: "Loading team..." });
287
+ }
288
+ if (error || !data?.team?.length) {
289
+ return /* @__PURE__ */ jsx5("div", { className: "idk-state", children: "No team members available." });
290
+ }
291
+ const gridStyle = { gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` };
292
+ return /* @__PURE__ */ jsx5(
293
+ "div",
294
+ {
295
+ className: ["idk-team", className].filter(Boolean).join(" "),
296
+ style: gridStyle,
297
+ children: data.team.map((member) => /* @__PURE__ */ jsx5(
298
+ TeamMember,
299
+ {
300
+ member,
301
+ showRole,
302
+ showEmail
303
+ },
304
+ member.id
305
+ ))
306
+ }
307
+ );
308
+ }
309
+
310
+ // src/components/ContactForm.tsx
311
+ import { useState as useState2 } from "react";
312
+ import { useMutation as useMutation2 } from "@tanstack/react-query";
313
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
314
+ var DEFAULT_FIELDS = ["name", "email", "phone", "message"];
315
+ function ContactForm({
316
+ fields = DEFAULT_FIELDS,
317
+ formType = "contact",
318
+ submitLabel = "Send Message",
319
+ className,
320
+ onSuccess,
321
+ onError
322
+ }) {
323
+ const config = usePlatformConfig();
324
+ const [formState, setFormState] = useState2({
325
+ name: "",
326
+ email: "",
327
+ phone: "",
328
+ subject: "",
329
+ message: ""
330
+ });
331
+ const mutation = useMutation2({
332
+ mutationFn: (payload) => apiRequest(config, "/contact", {
333
+ method: "POST",
334
+ body: JSON.stringify(payload)
335
+ }),
336
+ onSuccess: () => {
337
+ setFormState({ name: "", email: "", phone: "", subject: "", message: "" });
338
+ onSuccess?.();
339
+ },
340
+ onError: (error) => {
341
+ const message = error instanceof Error ? error.message : "Failed to submit form";
342
+ onError?.(message);
343
+ }
344
+ });
345
+ const handleChange = (field, value) => {
346
+ setFormState((prev) => ({ ...prev, [field]: value }));
347
+ };
348
+ const handleSubmit = (event) => {
349
+ event.preventDefault();
350
+ mutation.mutate({
351
+ name: formState.name,
352
+ email: formState.email,
353
+ phone: formState.phone || void 0,
354
+ subject: formState.subject || void 0,
355
+ message: formState.message,
356
+ formType
357
+ });
358
+ };
359
+ return /* @__PURE__ */ jsxs3("form", { className: ["idk-form", className].filter(Boolean).join(" "), onSubmit: handleSubmit, children: [
360
+ fields.includes("name") && /* @__PURE__ */ jsxs3("label", { className: "idk-form__field", children: [
361
+ /* @__PURE__ */ jsx6("span", { children: "Name" }),
362
+ /* @__PURE__ */ jsx6(
363
+ "input",
364
+ {
365
+ value: formState.name,
366
+ onChange: (event) => handleChange("name", event.target.value),
367
+ required: true
368
+ }
369
+ )
370
+ ] }),
371
+ fields.includes("email") && /* @__PURE__ */ jsxs3("label", { className: "idk-form__field", children: [
372
+ /* @__PURE__ */ jsx6("span", { children: "Email" }),
373
+ /* @__PURE__ */ jsx6(
374
+ "input",
375
+ {
376
+ type: "email",
377
+ value: formState.email,
378
+ onChange: (event) => handleChange("email", event.target.value),
379
+ required: true
380
+ }
381
+ )
382
+ ] }),
383
+ fields.includes("phone") && /* @__PURE__ */ jsxs3("label", { className: "idk-form__field", children: [
384
+ /* @__PURE__ */ jsx6("span", { children: "Phone" }),
385
+ /* @__PURE__ */ jsx6(
386
+ "input",
387
+ {
388
+ value: formState.phone,
389
+ onChange: (event) => handleChange("phone", event.target.value)
390
+ }
391
+ )
392
+ ] }),
393
+ fields.includes("subject") && /* @__PURE__ */ jsxs3("label", { className: "idk-form__field", children: [
394
+ /* @__PURE__ */ jsx6("span", { children: "Subject" }),
395
+ /* @__PURE__ */ jsx6(
396
+ "input",
397
+ {
398
+ value: formState.subject,
399
+ onChange: (event) => handleChange("subject", event.target.value)
400
+ }
401
+ )
402
+ ] }),
403
+ fields.includes("message") && /* @__PURE__ */ jsxs3("label", { className: "idk-form__field", children: [
404
+ /* @__PURE__ */ jsx6("span", { children: "Message" }),
405
+ /* @__PURE__ */ jsx6(
406
+ "textarea",
407
+ {
408
+ value: formState.message,
409
+ onChange: (event) => handleChange("message", event.target.value),
410
+ rows: 5,
411
+ required: true
412
+ }
413
+ )
414
+ ] }),
415
+ /* @__PURE__ */ jsx6("button", { type: "submit", className: "idk-button", disabled: mutation.isPending, children: mutation.isPending ? "Sending..." : submitLabel }),
416
+ mutation.isError ? /* @__PURE__ */ jsx6("p", { className: "idk-form__error", children: "Something went wrong. Please try again." }) : null,
417
+ mutation.isSuccess ? /* @__PURE__ */ jsx6("p", { className: "idk-form__success", children: "Thanks! We'll be in touch soon." }) : null
418
+ ] });
419
+ }
420
+
421
+ // src/components/BookingWidget.tsx
422
+ import { useMemo as useMemo2, useState as useState3 } from "react";
423
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
424
+ function BookingWidget({
425
+ className,
426
+ showStaffSelection = true,
427
+ onSuccess
428
+ }) {
429
+ const { data: servicesData, isLoading: servicesLoading } = useServices();
430
+ const { data: teamData } = useTeam();
431
+ const createBooking = useCreateBooking();
432
+ const [step, setStep] = useState3("service");
433
+ const [selectedService, setSelectedService] = useState3(null);
434
+ const [selectedStaff, setSelectedStaff] = useState3(null);
435
+ const [selectedDate, setSelectedDate] = useState3(null);
436
+ const [selectedTime, setSelectedTime] = useState3(null);
437
+ const [selectedEndTime, setSelectedEndTime] = useState3(null);
438
+ const [details, setDetails] = useState3({
439
+ name: "",
440
+ email: "",
441
+ phone: "",
442
+ notes: ""
443
+ });
444
+ const staffOptions = useMemo2(() => {
445
+ if (!selectedService) return [];
446
+ if (selectedService.assignedStaff && selectedService.assignedStaff.length > 0) {
447
+ return selectedService.assignedStaff;
448
+ }
449
+ return teamData?.team || [];
450
+ }, [selectedService, teamData]);
451
+ const availability = useAvailability({
452
+ serviceId: selectedService?.id || "",
453
+ staffId: selectedStaff?.id,
454
+ days: 7,
455
+ enabled: Boolean(selectedService)
456
+ });
457
+ const requiresStaff = showStaffSelection || selectedService?.requiresStaffSelection || selectedService?.schedulingType === "customer-choice";
458
+ const handleSubmit = async (event) => {
459
+ event.preventDefault();
460
+ if (!selectedService || !selectedDate || !selectedTime || !selectedEndTime) return;
461
+ const start = /* @__PURE__ */ new Date(`${selectedDate}T${selectedTime}:00`);
462
+ const end = /* @__PURE__ */ new Date(`${selectedDate}T${selectedEndTime}:00`);
463
+ const result = await createBooking.mutateAsync({
464
+ serviceId: selectedService.id,
465
+ staffId: selectedStaff?.id,
466
+ startTime: start.toISOString(),
467
+ endTime: end.toISOString(),
468
+ customerName: details.name,
469
+ customerEmail: details.email,
470
+ customerPhone: details.phone || void 0,
471
+ customerNotes: details.notes || void 0
472
+ });
473
+ setStep("done");
474
+ onSuccess?.(result?.booking);
475
+ };
476
+ if (servicesLoading) {
477
+ return /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Loading booking options..." });
478
+ }
479
+ if (!servicesData?.services?.length) {
480
+ return /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No services available." });
481
+ }
482
+ return /* @__PURE__ */ jsxs4("div", { className: ["idk-booking", className].filter(Boolean).join(" "), children: [
483
+ step === "service" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
484
+ /* @__PURE__ */ jsx7("h3", { children: "Select a service" }),
485
+ /* @__PURE__ */ jsx7("div", { className: "idk-services idk-services--list", children: servicesData.services.map((service) => /* @__PURE__ */ jsxs4(
486
+ "button",
487
+ {
488
+ type: "button",
489
+ className: "idk-card idk-card--clickable",
490
+ onClick: () => {
491
+ setSelectedService(service);
492
+ if (requiresStaff) {
493
+ setStep("staff");
494
+ } else {
495
+ setStep("time");
496
+ }
497
+ },
498
+ children: [
499
+ /* @__PURE__ */ jsxs4("div", { className: "idk-card__header", children: [
500
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__title", children: service.name }),
501
+ typeof service.price === "number" && service.price > 0 ? /* @__PURE__ */ jsxs4("span", { className: "idk-card__price", children: [
502
+ "$",
503
+ (service.price / 100).toFixed(2)
504
+ ] }) : null
505
+ ] }),
506
+ /* @__PURE__ */ jsxs4("div", { className: "idk-card__meta", children: [
507
+ service.duration,
508
+ " min"
509
+ ] })
510
+ ]
511
+ },
512
+ service.id
513
+ )) })
514
+ ] }),
515
+ step === "staff" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
516
+ /* @__PURE__ */ jsx7("h3", { children: "Select a team member" }),
517
+ /* @__PURE__ */ jsx7("div", { className: "idk-team", children: staffOptions.map((staff) => /* @__PURE__ */ jsxs4(
518
+ "button",
519
+ {
520
+ type: "button",
521
+ className: "idk-card idk-card--clickable",
522
+ onClick: () => {
523
+ setSelectedStaff(staff);
524
+ setStep("time");
525
+ },
526
+ children: [
527
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__title", children: staff.name }),
528
+ /* @__PURE__ */ jsx7("span", { className: "idk-card__meta", children: staff.role || "Staff" })
529
+ ]
530
+ },
531
+ staff.id
532
+ )) }),
533
+ /* @__PURE__ */ jsx7(
534
+ "button",
535
+ {
536
+ type: "button",
537
+ className: "idk-link",
538
+ onClick: () => {
539
+ setSelectedStaff(null);
540
+ setStep("time");
541
+ },
542
+ children: "Continue without selecting"
543
+ }
544
+ )
545
+ ] }),
546
+ step === "time" && /* @__PURE__ */ jsxs4("div", { className: "idk-booking__step", children: [
547
+ /* @__PURE__ */ jsx7("h3", { children: "Select a time" }),
548
+ availability.isLoading ? /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Loading availability..." }) : availability.data ? /* @__PURE__ */ jsxs4("div", { className: "idk-availability", children: [
549
+ /* @__PURE__ */ jsx7("div", { className: "idk-availability__dates", children: availability.data.dates.map((entry) => /* @__PURE__ */ jsx7(
550
+ "button",
551
+ {
552
+ type: "button",
553
+ className: entry.date === selectedDate ? "is-active" : void 0,
554
+ onClick: () => {
555
+ setSelectedDate(entry.date);
556
+ setSelectedTime(null);
557
+ setSelectedEndTime(null);
558
+ },
559
+ children: entry.date
560
+ },
561
+ entry.date
562
+ )) }),
563
+ /* @__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(
564
+ "button",
565
+ {
566
+ type: "button",
567
+ className: slot.time === selectedTime ? "is-active" : void 0,
568
+ onClick: () => {
569
+ const date = selectedDate || availability.data?.dates[0]?.date;
570
+ setSelectedDate(date || null);
571
+ setSelectedTime(slot.time);
572
+ setSelectedEndTime(slot.endTime);
573
+ setStep("details");
574
+ },
575
+ children: slot.time
576
+ },
577
+ `${slot.time}-${slot.endTime}`
578
+ )) })
579
+ ] }) : /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "No availability found." })
580
+ ] }),
581
+ step === "details" && /* @__PURE__ */ jsxs4("form", { className: "idk-form", onSubmit: handleSubmit, children: [
582
+ /* @__PURE__ */ jsx7("h3", { children: "Your details" }),
583
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
584
+ /* @__PURE__ */ jsx7("span", { children: "Name" }),
585
+ /* @__PURE__ */ jsx7(
586
+ "input",
587
+ {
588
+ value: details.name,
589
+ onChange: (event) => setDetails((prev) => ({ ...prev, name: event.target.value })),
590
+ required: true
591
+ }
592
+ )
593
+ ] }),
594
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
595
+ /* @__PURE__ */ jsx7("span", { children: "Email" }),
596
+ /* @__PURE__ */ jsx7(
597
+ "input",
598
+ {
599
+ type: "email",
600
+ value: details.email,
601
+ onChange: (event) => setDetails((prev) => ({ ...prev, email: event.target.value })),
602
+ required: true
603
+ }
604
+ )
605
+ ] }),
606
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
607
+ /* @__PURE__ */ jsx7("span", { children: "Phone" }),
608
+ /* @__PURE__ */ jsx7(
609
+ "input",
610
+ {
611
+ value: details.phone,
612
+ onChange: (event) => setDetails((prev) => ({ ...prev, phone: event.target.value }))
613
+ }
614
+ )
615
+ ] }),
616
+ /* @__PURE__ */ jsxs4("label", { className: "idk-form__field", children: [
617
+ /* @__PURE__ */ jsx7("span", { children: "Notes" }),
618
+ /* @__PURE__ */ jsx7(
619
+ "textarea",
620
+ {
621
+ rows: 4,
622
+ value: details.notes,
623
+ onChange: (event) => setDetails((prev) => ({ ...prev, notes: event.target.value }))
624
+ }
625
+ )
626
+ ] }),
627
+ /* @__PURE__ */ jsx7("button", { type: "submit", className: "idk-button", disabled: createBooking.isPending, children: createBooking.isPending ? "Booking..." : "Confirm booking" })
628
+ ] }),
629
+ step === "done" && /* @__PURE__ */ jsx7("div", { className: "idk-state", children: "Booking confirmed. You'll receive a confirmation email shortly." })
630
+ ] });
631
+ }
632
+
633
+ // src/components/AvailabilityPicker.tsx
634
+ import { useState as useState4 } from "react";
635
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
636
+ function AvailabilityPicker({
637
+ serviceId,
638
+ staffId,
639
+ date,
640
+ days = 7,
641
+ className,
642
+ onSelect
643
+ }) {
644
+ const [selectedDate, setSelectedDate] = useState4(date || null);
645
+ const [selectedTime, setSelectedTime] = useState4(null);
646
+ const { data, isLoading } = useAvailability({
647
+ serviceId,
648
+ staffId,
649
+ date,
650
+ days
651
+ });
652
+ if (isLoading) {
653
+ return /* @__PURE__ */ jsx8("div", { className: "idk-state", children: "Loading availability..." });
654
+ }
655
+ if (!data?.dates?.length) {
656
+ return /* @__PURE__ */ jsx8("div", { className: "idk-state", children: "No availability found." });
657
+ }
658
+ const activeDate = selectedDate || data.dates[0]?.date;
659
+ const activeSlots = data.dates.find((entry) => entry.date === activeDate)?.slots || [];
660
+ return /* @__PURE__ */ jsxs5("div", { className: ["idk-availability", className].filter(Boolean).join(" "), children: [
661
+ /* @__PURE__ */ jsx8("div", { className: "idk-availability__dates", children: data.dates.map((entry) => /* @__PURE__ */ jsx8(
662
+ "button",
663
+ {
664
+ type: "button",
665
+ className: entry.date === activeDate ? "is-active" : void 0,
666
+ onClick: () => {
667
+ setSelectedDate(entry.date);
668
+ setSelectedTime(null);
669
+ },
670
+ children: entry.date
671
+ },
672
+ entry.date
673
+ )) }),
674
+ /* @__PURE__ */ jsx8("div", { className: "idk-availability__slots", children: activeSlots.filter((slot) => slot.available).length === 0 ? /* @__PURE__ */ jsx8("div", { className: "idk-state", children: "No slots available." }) : activeSlots.filter((slot) => slot.available).map((slot) => /* @__PURE__ */ jsx8(
675
+ "button",
676
+ {
677
+ type: "button",
678
+ className: selectedTime === slot.time ? "is-active" : void 0,
679
+ onClick: () => {
680
+ setSelectedTime(slot.time);
681
+ if (onSelect && activeDate) {
682
+ onSelect({
683
+ date: activeDate,
684
+ time: slot.time,
685
+ endTime: slot.endTime
686
+ });
687
+ }
688
+ },
689
+ children: slot.time
690
+ },
691
+ `${activeDate}-${slot.time}`
692
+ )) })
693
+ ] });
694
+ }
695
+
696
+ // src/components/ServicePicker.tsx
697
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
698
+ function ServicePicker({
699
+ services,
700
+ selectedId,
701
+ onSelect,
702
+ className
703
+ }) {
704
+ return /* @__PURE__ */ jsx9("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: services.map((service) => /* @__PURE__ */ jsxs6(
705
+ "button",
706
+ {
707
+ type: "button",
708
+ className: selectedId === service.id ? "is-active" : void 0,
709
+ onClick: () => onSelect?.(service),
710
+ children: [
711
+ /* @__PURE__ */ jsx9("span", { children: service.name }),
712
+ /* @__PURE__ */ jsxs6("small", { children: [
713
+ service.duration,
714
+ " min"
715
+ ] })
716
+ ]
717
+ },
718
+ service.id
719
+ )) });
720
+ }
721
+
722
+ // src/components/StaffPicker.tsx
723
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
724
+ function StaffPicker({
725
+ staff,
726
+ selectedId,
727
+ onSelect,
728
+ className
729
+ }) {
730
+ return /* @__PURE__ */ jsx10("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: staff.map((member) => /* @__PURE__ */ jsxs7(
731
+ "button",
732
+ {
733
+ type: "button",
734
+ className: selectedId === member.id ? "is-active" : void 0,
735
+ onClick: () => onSelect?.(member),
736
+ children: [
737
+ /* @__PURE__ */ jsx10("span", { children: member.name }),
738
+ /* @__PURE__ */ jsx10("small", { children: member.role || "Staff" })
739
+ ]
740
+ },
741
+ member.id
742
+ )) });
743
+ }
744
+
745
+ // src/components/DatePicker.tsx
746
+ import { jsx as jsx11 } from "react/jsx-runtime";
747
+ function DatePicker({
748
+ dates,
749
+ selectedDate,
750
+ onSelect,
751
+ className
752
+ }) {
753
+ return /* @__PURE__ */ jsx11("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: dates.map((date) => /* @__PURE__ */ jsx11(
754
+ "button",
755
+ {
756
+ type: "button",
757
+ className: selectedDate === date ? "is-active" : void 0,
758
+ onClick: () => onSelect?.(date),
759
+ children: date
760
+ },
761
+ date
762
+ )) });
763
+ }
764
+
765
+ // src/components/TimePicker.tsx
766
+ import { jsx as jsx12 } from "react/jsx-runtime";
767
+ function TimePicker({
768
+ slots,
769
+ selectedTime,
770
+ onSelect,
771
+ className
772
+ }) {
773
+ return /* @__PURE__ */ jsx12("div", { className: ["idk-picker", className].filter(Boolean).join(" "), children: slots.map((slot) => /* @__PURE__ */ jsx12(
774
+ "button",
775
+ {
776
+ type: "button",
777
+ className: selectedTime === slot.time ? "is-active" : void 0,
778
+ disabled: !slot.available,
779
+ onClick: () => onSelect?.(slot),
780
+ children: slot.time
781
+ },
782
+ `${slot.time}-${slot.endTime}`
783
+ )) });
784
+ }
785
+ export {
786
+ AvailabilityPicker,
787
+ BookingWidget,
788
+ ContactForm,
789
+ DatePicker,
790
+ PlatformProvider,
791
+ ServiceCard,
792
+ ServicePicker,
793
+ ServicesList,
794
+ StaffPicker,
795
+ TeamGrid,
796
+ TeamMember,
797
+ TimePicker,
798
+ useAvailability,
799
+ useBookingLookup,
800
+ useCancelBooking,
801
+ useCreateBooking,
802
+ useServices,
803
+ useTeam,
804
+ useTenant
805
+ };
806
+ //# sourceMappingURL=index.js.map