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