@clicka1/booking 0.2.2

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.
@@ -0,0 +1,941 @@
1
+ 'use client';
2
+ import { useT, serviceMatchesCategory } from './chunk-HA7DFBYI.js';
3
+ import { X, Plus, ChevronDown, Check, User, Loader2 } from 'lucide-react';
4
+ import { useMemo, useState, useRef, useEffect } from 'react';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+
7
+ // ../../lib/booking-modal-catalog.ts
8
+ function getBookingRowIndex(bookingRows, catalogId, variantLabel) {
9
+ if (variantLabel) {
10
+ return bookingRows.findIndex((row) => row.id === `${catalogId}::${variantLabel}`);
11
+ }
12
+ const exact = bookingRows.findIndex((row) => row.id === catalogId);
13
+ if (exact >= 0) return exact;
14
+ return bookingRows.findIndex((row) => row.id.startsWith(`${catalogId}::`));
15
+ }
16
+ function isCatalogServiceSelected(selectedIdxs, bookingRows, catalog, variantLabel) {
17
+ if (catalog.variants?.length) {
18
+ const label = variantLabel ?? catalog.variants[0]?.label;
19
+ const idx2 = label ? getBookingRowIndex(bookingRows, catalog.id, label) : -1;
20
+ return idx2 >= 0 && selectedIdxs.includes(idx2);
21
+ }
22
+ const idx = getBookingRowIndex(bookingRows, catalog.id);
23
+ return idx >= 0 && selectedIdxs.includes(idx);
24
+ }
25
+ function getCatalogDisplayPriceDuration(catalog, variantLabel) {
26
+ const variants = catalog.variants ?? [];
27
+ if (variants.length > 0) {
28
+ const label = variantLabel ?? variants[0].label;
29
+ const variant = variants.find((v) => v.label === label) ?? variants[0];
30
+ return {
31
+ price: Number(variant.price ?? catalog.price ?? 0) || 0,
32
+ duration: Math.max(5, Number(variant.duration ?? catalog.duration ?? 30) || 30)
33
+ };
34
+ }
35
+ return {
36
+ price: Number(catalog.price ?? 0) || 0,
37
+ duration: Math.max(5, Number(catalog.duration ?? 30) || 30)
38
+ };
39
+ }
40
+
41
+ // ../../lib/cancellation-policy.ts
42
+ function formatPolicySummary(opts) {
43
+ const { cancelPolicyHours, cancelPolicyAction, depositAmountEuros } = opts;
44
+ const window2 = cancelPolicyHours >= 48 ? `${Math.round(cancelPolicyHours / 24)} \u0434\u043D\u0438` : `${cancelPolicyHours} \u0447\u0430\u0441\u0430`;
45
+ if (cancelPolicyAction === "full_refund") {
46
+ return `\u0411\u0435\u0437\u043F\u043B\u0430\u0442\u043D\u043E \u043E\u0442\u043A\u0430\u0437\u0432\u0430\u043D\u0435 \u0434\u043E ${window2} \u043F\u0440\u0435\u0434\u0438 \u0447\u0430\u0441\u0430. \u0421\u043B\u0435\u0434 \u0441\u0440\u043E\u043A\u0430 \u2014 \u043F\u044A\u043B\u0435\u043D refund.`;
47
+ }
48
+ if (cancelPolicyAction === "keep_full") {
49
+ return `\u0411\u0435\u0437\u043F\u043B\u0430\u0442\u043D\u043E \u043E\u0442\u043A\u0430\u0437\u0432\u0430\u043D\u0435 \u0434\u043E ${window2} \u043F\u0440\u0435\u0434\u0438 \u0447\u0430\u0441\u0430. \u0421\u043B\u0435\u0434 \u0441\u0440\u043E\u043A\u0430 \u043F\u043B\u0430\u0442\u0435\u043D\u0430\u0442\u0430 \u0441\u0443\u043C\u0430 \u043D\u0435 \u0441\u0435 \u0432\u0440\u044A\u0449\u0430.`;
50
+ }
51
+ const depositStr = depositAmountEuros ? ` (\u20AC${depositAmountEuros})` : "";
52
+ return `\u0411\u0435\u0437\u043F\u043B\u0430\u0442\u043D\u043E \u043E\u0442\u043A\u0430\u0437\u0432\u0430\u043D\u0435 \u0434\u043E ${window2} \u043F\u0440\u0435\u0434\u0438 \u0447\u0430\u0441\u0430. \u0421\u043B\u0435\u0434 \u0441\u0440\u043E\u043A\u0430 \u0434\u0435\u043F\u043E\u0437\u0438\u0442\u044A\u0442${depositStr} \u0441\u0435 \u0437\u0430\u0434\u044A\u0440\u0436\u0430.`;
53
+ }
54
+ function SalonServiceCategoryTabs({
55
+ categories,
56
+ selectedId,
57
+ onSelect,
58
+ size = "md",
59
+ className = "",
60
+ accentFill = "#111111"
61
+ }) {
62
+ const named = categories.filter((c) => c.id != null);
63
+ if (named.length === 0) return null;
64
+ const textClass = size === "sm" ? "text-[12px]" : "text-sm";
65
+ return /* @__PURE__ */ jsx(
66
+ "div",
67
+ {
68
+ className: `flex items-center gap-4 overflow-x-auto pb-0.5 scrollbar-none ${textClass} ${className}`.trim(),
69
+ role: "tablist",
70
+ "aria-label": "\u041A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438 \u0443\u0441\u043B\u0443\u0433\u0438",
71
+ children: categories.map((cat) => {
72
+ const active = selectedId === cat.id;
73
+ return /* @__PURE__ */ jsxs(
74
+ "span",
75
+ {
76
+ role: "tab",
77
+ "aria-selected": active,
78
+ tabIndex: 0,
79
+ onClick: () => onSelect(cat.id),
80
+ onKeyDown: (e) => {
81
+ if (e.key === "Enter" || e.key === " ") {
82
+ e.preventDefault();
83
+ onSelect(cat.id);
84
+ }
85
+ },
86
+ className: `relative inline-block shrink-0 cursor-pointer select-none whitespace-nowrap pb-1.5 font-bold text-black transition ${active ? "" : "hover:opacity-80"}`,
87
+ children: [
88
+ cat.label,
89
+ /* @__PURE__ */ jsx(
90
+ "span",
91
+ {
92
+ "aria-hidden": true,
93
+ className: "absolute bottom-0 left-0 h-[2px] rounded-full transition-[width,opacity] duration-200",
94
+ style: {
95
+ width: active ? "100%" : "0%",
96
+ opacity: active ? 1 : 0,
97
+ backgroundImage: accentFill
98
+ }
99
+ }
100
+ )
101
+ ]
102
+ },
103
+ cat.id ?? "all"
104
+ );
105
+ })
106
+ }
107
+ );
108
+ }
109
+ function BookingSuccessView({
110
+ serviceName,
111
+ dateLabel,
112
+ time,
113
+ salonName,
114
+ onClose
115
+ }) {
116
+ const t = useT();
117
+ const [show, setShow] = useState(false);
118
+ useEffect(() => {
119
+ const t2 = setTimeout(() => setShow(true), 50);
120
+ return () => clearTimeout(t2);
121
+ }, []);
122
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col items-center justify-center px-6 py-10", children: /* @__PURE__ */ jsxs(
123
+ "div",
124
+ {
125
+ className: `flex flex-col items-center transition-all duration-700 ease-out ${show ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"}`,
126
+ children: [
127
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-6", children: [
128
+ /* @__PURE__ */ jsx(
129
+ "div",
130
+ {
131
+ className: `flex h-20 w-20 items-center justify-center rounded-full transition-all duration-500 ease-out ${show ? "scale-100" : "scale-50"}`,
132
+ style: { background: "linear-gradient(135deg, #22c55e 0%, #16a34a 100%)" },
133
+ children: /* @__PURE__ */ jsx(
134
+ "svg",
135
+ {
136
+ viewBox: "0 0 24 24",
137
+ fill: "none",
138
+ className: "h-10 w-10",
139
+ "aria-hidden": true,
140
+ children: /* @__PURE__ */ jsx(
141
+ "path",
142
+ {
143
+ d: "M5 13l4 4L19 7",
144
+ stroke: "white",
145
+ strokeWidth: "2.5",
146
+ strokeLinecap: "round",
147
+ strokeLinejoin: "round",
148
+ className: show ? "animate-[draw_0.5s_ease-out_0.3s_forwards]" : "",
149
+ style: {
150
+ strokeDasharray: 24,
151
+ strokeDashoffset: show ? 0 : 24,
152
+ transition: "stroke-dashoffset 0.5s ease-out 0.3s"
153
+ }
154
+ }
155
+ )
156
+ }
157
+ )
158
+ }
159
+ ),
160
+ /* @__PURE__ */ jsx(
161
+ "div",
162
+ {
163
+ className: `absolute inset-0 rounded-full transition-all duration-700 ease-out ${show ? "scale-150 opacity-0" : "scale-100 opacity-30"}`,
164
+ style: { background: "linear-gradient(135deg, #22c55e 0%, #16a34a 100%)" },
165
+ "aria-hidden": true
166
+ }
167
+ )
168
+ ] }),
169
+ /* @__PURE__ */ jsx(
170
+ "h3",
171
+ {
172
+ className: `text-center text-[22px] font-semibold leading-tight tracking-tight text-[#111] transition-all delay-200 duration-500 ${show ? "translate-y-0 opacity-100" : "translate-y-3 opacity-0"}`,
173
+ children: t("booking.success.confirmed")
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsxs(
177
+ "p",
178
+ {
179
+ className: `mt-2 text-center text-[15px] leading-relaxed text-black/45 transition-all delay-300 duration-500 ${show ? "translate-y-0 opacity-100" : "translate-y-3 opacity-0"}`,
180
+ children: [
181
+ t("booking.success.waitingAt"),
182
+ " ",
183
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-black/60", children: salonName })
184
+ ]
185
+ }
186
+ ),
187
+ /* @__PURE__ */ jsx(
188
+ "div",
189
+ {
190
+ className: `mt-6 w-full max-w-[320px] overflow-hidden rounded-2xl border border-black/[0.06] bg-white shadow-[0_2px_12px_rgba(0,0,0,0.08)] transition-all delay-[400ms] duration-500 ${show ? "translate-y-0 opacity-100" : "translate-y-3 opacity-0"}`,
191
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-5 py-4", children: [
192
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
193
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-black/[0.04]", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 20", fill: "currentColor", className: "h-4 w-4 text-black/40", "aria-hidden": true, children: [
194
+ /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z", clipRule: "evenodd" }),
195
+ /* @__PURE__ */ jsx("path", { d: "M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" })
196
+ ] }) }),
197
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
198
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium uppercase tracking-wide text-black/30", children: t("booking.success.labelService") }),
199
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[14px] font-medium leading-snug text-[#111]", children: serviceName })
200
+ ] })
201
+ ] }),
202
+ /* @__PURE__ */ jsx("div", { className: "h-px bg-black/[0.05]" }),
203
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
204
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-black/[0.04]", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", fill: "currentColor", className: "h-4 w-4 text-black/40", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M5.75 2a.75.75 0 01.75.75V4h7V2.75a.75.75 0 011.5 0V4h.25A2.75 2.75 0 0118 6.75v8.5A2.75 2.75 0 0115.25 18H4.75A2.75 2.75 0 012 15.25v-8.5A2.75 2.75 0 014.75 4H5V2.75A.75.75 0 015.75 2zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75z", clipRule: "evenodd" }) }) }),
205
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
206
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium uppercase tracking-wide text-black/30", children: t("booking.success.labelWhen") }),
207
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[14px] font-medium leading-snug text-[#111]", children: t("booking.success.timeFormat", { date: dateLabel, time }) })
208
+ ] })
209
+ ] })
210
+ ] })
211
+ }
212
+ ),
213
+ /* @__PURE__ */ jsx(
214
+ "button",
215
+ {
216
+ type: "button",
217
+ onClick: onClose,
218
+ className: `mt-8 w-full max-w-[320px] rounded-full bg-[#111] py-3.5 text-[15px] font-semibold text-white shadow-[0_4px_14px_rgba(0,0,0,0.15)] transition-all delay-500 duration-500 active:scale-[0.98] ${show ? "translate-y-0 opacity-100" : "translate-y-3 opacity-0"}`,
219
+ children: t("booking.success.done")
220
+ }
221
+ )
222
+ ]
223
+ }
224
+ ) });
225
+ }
226
+ var cardShadow = "shadow-[0_2px_6px_rgba(0,0,0,0.14),0_10px_32px_rgba(0,0,0,0.18),0_1px_2px_rgba(0,0,0,0.1)]";
227
+ var gradientCtaShadow = "shadow-[0_8px_28px_rgba(0,0,0,0.18)]";
228
+ var gradientRingShadow = "shadow-[0_2px_10px_rgba(0,0,0,0.08)]";
229
+ var fieldClass = `mt-1.5 block w-full min-w-0 max-w-full box-border rounded-2xl border border-black/[0.06] bg-white px-3.5 py-3 text-[16px] leading-tight text-[#111] touch-manipulation ${cardShadow} outline-none transition focus:border-[color:var(--salon-primary)]/40 focus:ring-2 focus:ring-[color:var(--salon-primary)]/12`;
230
+ function addMinutesToTime(time, minutesToAdd) {
231
+ const [h, m] = time.split(":").map(Number);
232
+ if (!Number.isFinite(h) || !Number.isFinite(m)) return time;
233
+ const total = h * 60 + m + minutesToAdd;
234
+ const outH = Math.floor(total / 60) % 24;
235
+ const outM = total % 60;
236
+ return `${String(outH).padStart(2, "0")}:${String(outM).padStart(2, "0")}`;
237
+ }
238
+ function ServiceDescription({ text }) {
239
+ const description = text?.trim();
240
+ if (!description) return null;
241
+ return /* @__PURE__ */ jsx("p", { className: "mt-1 line-clamp-3 text-[12px] leading-relaxed text-black/50", children: description });
242
+ }
243
+ function SalonBookingModal({
244
+ open,
245
+ primaryColor,
246
+ accentGradient,
247
+ locale = "bg-BG",
248
+ formatPrice,
249
+ serviceCatalog,
250
+ services,
251
+ categoryTabs,
252
+ selectedServiceIdxs,
253
+ selectedDate,
254
+ selectedTime,
255
+ totalDuration,
256
+ totalPrice,
257
+ clientName,
258
+ clientPhone,
259
+ clientEmail,
260
+ notes,
261
+ salonName,
262
+ termsHref,
263
+ privacyHref,
264
+ minDate,
265
+ maxDate,
266
+ timeSlots,
267
+ paymentType = "none",
268
+ depositAmount,
269
+ cancelPolicyHours,
270
+ cancelPolicyAction,
271
+ isSubmitting,
272
+ bookingError,
273
+ bookingSuccess,
274
+ bookingSuccessDetails,
275
+ onClose,
276
+ onToggleService,
277
+ onDateChange,
278
+ onTimeChange,
279
+ onClientNameChange,
280
+ onClientPhoneChange,
281
+ onClientEmailChange,
282
+ onNotesChange,
283
+ onSubmit,
284
+ staffMembers = [],
285
+ selectedStaffMemberId,
286
+ onStaffMemberChange,
287
+ directStaffName
288
+ }) {
289
+ const t = useT();
290
+ const accentFill = useMemo(
291
+ () => accentGradient ?? `linear-gradient(135deg, ${primaryColor}, ${primaryColor})`,
292
+ [accentGradient, primaryColor]
293
+ );
294
+ const accentFillStyle = useMemo(
295
+ () => ({ backgroundImage: accentFill }),
296
+ [accentFill]
297
+ );
298
+ const accentBorderStyle = useMemo(
299
+ () => ({
300
+ border: "1px solid transparent",
301
+ backgroundImage: `linear-gradient(#ffffff, #ffffff), ${accentFill}`,
302
+ backgroundOrigin: "border-box",
303
+ backgroundClip: "padding-box, border-box"
304
+ }),
305
+ [accentFill]
306
+ );
307
+ const fmtPrice = useMemo(
308
+ () => formatPrice ?? ((n) => `${Number.isInteger(n) ? n : n.toFixed(2)} \u20AC`),
309
+ [formatPrice]
310
+ );
311
+ const isTeam = staffMembers.length > 0 && !directStaffName;
312
+ const [step, setStep] = useState(1);
313
+ const [selectedCategory, setSelectedCategory] = useState(null);
314
+ const [browseAllServices, setBrowseAllServices] = useState(true);
315
+ const [selectedVariantByServiceId, setSelectedVariantByServiceId] = useState({});
316
+ const [variantDropdownOpenForServiceId, setVariantDropdownOpenForServiceId] = useState(null);
317
+ const prevOpenRef = useRef(false);
318
+ useEffect(() => {
319
+ if (!open) return;
320
+ setStep(1);
321
+ setSelectedCategory(null);
322
+ setVariantDropdownOpenForServiceId(null);
323
+ const initial = {};
324
+ for (const service of serviceCatalog) {
325
+ const variants = service.variants ?? [];
326
+ if (variants.length > 0) initial[service.id] = variants[0].label;
327
+ }
328
+ setSelectedVariantByServiceId(initial);
329
+ }, [open, serviceCatalog]);
330
+ const selectedServiceIds = useMemo(() => {
331
+ return selectedServiceIdxs.map((idx) => services[idx]?.id).filter((id) => Boolean(id));
332
+ }, [selectedServiceIdxs, services]);
333
+ const eligibleStaff = useMemo(() => {
334
+ if (!isTeam) return [];
335
+ if (selectedServiceIds.length === 0) return staffMembers;
336
+ return staffMembers.filter(
337
+ (sm) => selectedServiceIds.every((sid) => sm.serviceIds.includes(sid))
338
+ );
339
+ }, [isTeam, staffMembers, selectedServiceIds]);
340
+ useEffect(() => {
341
+ if (!isTeam || eligibleStaff.length !== 1) return;
342
+ onStaffMemberChange?.(eligibleStaff[0].id);
343
+ }, [isTeam, eligibleStaff, onStaffMemberChange]);
344
+ useEffect(() => {
345
+ if (open && !prevOpenRef.current) {
346
+ setBrowseAllServices(selectedServiceIdxs.length === 0);
347
+ }
348
+ prevOpenRef.current = open;
349
+ }, [open, selectedServiceIdxs.length]);
350
+ useEffect(() => {
351
+ if (selectedServiceIdxs.length === 0) {
352
+ setBrowseAllServices(true);
353
+ } else {
354
+ setBrowseAllServices(false);
355
+ }
356
+ }, [selectedServiceIdxs.length]);
357
+ const hasServices = selectedServiceIdxs.length > 0;
358
+ const endTime = useMemo(
359
+ () => selectedTime ? addMinutesToTime(selectedTime, Math.max(5, totalDuration || 0)) : "",
360
+ [selectedTime, totalDuration]
361
+ );
362
+ const selectedServices = useMemo(
363
+ () => selectedServiceIdxs.map((idx) => services[idx]).filter((svc) => Boolean(svc)),
364
+ [selectedServiceIdxs, services]
365
+ );
366
+ const visibleCatalog = useMemo(
367
+ () => serviceCatalog.filter((service) => serviceMatchesCategory(service, selectedCategory)),
368
+ [serviceCatalog, selectedCategory]
369
+ );
370
+ useEffect(() => {
371
+ if (!selectedCategory) return;
372
+ const hasCategory = serviceCatalog.some((svc) => serviceMatchesCategory(svc, selectedCategory));
373
+ if (!hasCategory) setSelectedCategory(null);
374
+ }, [serviceCatalog, selectedCategory]);
375
+ function toggleCatalogService(service) {
376
+ const variants = service.variants ?? [];
377
+ const variantLabel = variants.length > 0 ? selectedVariantByServiceId[service.id] ?? variants[0].label : null;
378
+ const idx = getBookingRowIndex(services, service.id, variantLabel);
379
+ if (idx < 0) return;
380
+ onToggleService(idx);
381
+ }
382
+ const dateOptions = useMemo(() => {
383
+ if (!minDate || !maxDate) return [];
384
+ const out = [];
385
+ const start = /* @__PURE__ */ new Date(`${minDate}T12:00:00`);
386
+ const end = /* @__PURE__ */ new Date(`${maxDate}T12:00:00`);
387
+ const cursor = new Date(start);
388
+ while (cursor <= end && out.length < 45) {
389
+ const iso = `${cursor.getFullYear()}-${String(cursor.getMonth() + 1).padStart(2, "0")}-${String(cursor.getDate()).padStart(2, "0")}`;
390
+ out.push({
391
+ iso,
392
+ weekday: cursor.toLocaleDateString(locale, { weekday: "short" }),
393
+ day: cursor.toLocaleDateString(locale, { day: "numeric", month: "short" })
394
+ });
395
+ cursor.setDate(cursor.getDate() + 1);
396
+ }
397
+ return out;
398
+ }, [minDate, maxDate, locale]);
399
+ function goToStep(target) {
400
+ if (isTeam) {
401
+ if (target >= 2 && !hasServices) return;
402
+ if (target >= 3 && !selectedStaffMemberId) return;
403
+ if (target >= 4 && (!hasServices || !selectedTime)) return;
404
+ } else {
405
+ if (target >= 2 && !hasServices) return;
406
+ if (target >= 3 && (!hasServices || !selectedTime)) return;
407
+ }
408
+ setStep(target);
409
+ }
410
+ const stepLabels = isTeam ? [
411
+ { n: 1, label: t("booking.modal.stepService") },
412
+ { n: 2, label: t("booking.modal.stepSpecialist") },
413
+ { n: 3, label: t("booking.modal.stepTime") },
414
+ { n: 4, label: t("booking.modal.stepDetails") }
415
+ ] : [
416
+ { n: 1, label: t("booking.modal.stepService") },
417
+ { n: 2, label: t("booking.modal.stepTime") },
418
+ { n: 3, label: t("booking.modal.stepDetails") }
419
+ ];
420
+ function requestClose() {
421
+ if (typeof window !== "undefined") {
422
+ const shouldClose = window.confirm(t("booking.modal.confirmClose"));
423
+ if (!shouldClose) return;
424
+ }
425
+ onClose();
426
+ }
427
+ useEffect(() => {
428
+ if (!open || typeof document === "undefined") return;
429
+ document.documentElement.style.setProperty("overflow", "hidden");
430
+ document.documentElement.style.setProperty("background-color", "#ffffff");
431
+ document.body.style.setProperty("overflow", "hidden");
432
+ document.body.style.setProperty("position", "fixed");
433
+ document.body.style.setProperty("inset", "0");
434
+ document.body.style.setProperty("width", "100%");
435
+ document.body.style.setProperty("background-color", "#ffffff");
436
+ const scrollY = window.scrollY;
437
+ document.body.style.setProperty("top", `-${scrollY}px`);
438
+ return () => {
439
+ document.documentElement.style.removeProperty("overflow");
440
+ document.documentElement.style.removeProperty("background-color");
441
+ document.body.style.removeProperty("overflow");
442
+ document.body.style.removeProperty("position");
443
+ document.body.style.removeProperty("inset");
444
+ document.body.style.removeProperty("width");
445
+ document.body.style.removeProperty("background-color");
446
+ document.body.style.removeProperty("top");
447
+ window.scrollTo(0, scrollY);
448
+ };
449
+ }, [open]);
450
+ if (!open) return null;
451
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[110] overflow-hidden bg-white sm:bg-transparent", role: "presentation", children: [
452
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 hidden bg-black/30 backdrop-blur-sm sm:block", "aria-hidden": true }),
453
+ /* @__PURE__ */ jsxs(
454
+ "div",
455
+ {
456
+ role: "dialog",
457
+ "aria-modal": true,
458
+ "aria-label": t("booking.modal.ariaLabel"),
459
+ className: "absolute inset-x-0 bottom-0 z-10 mx-auto flex h-[100dvh] w-full max-w-none flex-col overflow-hidden bg-white sm:inset-x-auto sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:h-auto sm:max-h-[88vh] sm:w-full sm:max-w-md sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-[1.6rem] sm:bg-white sm:shadow-[0_25px_60px_rgba(0,0,0,0.12)]",
460
+ onClick: (e) => e.stopPropagation(),
461
+ children: [
462
+ /* @__PURE__ */ jsx("div", { className: "mx-auto mt-2 h-1 w-10 shrink-0 rounded-full bg-black/10 sm:hidden", "aria-hidden": true }),
463
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex shrink-0 items-center justify-between gap-2 bg-white px-4 pb-3 pt-3.5 sm:px-5", children: [
464
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [
465
+ /* @__PURE__ */ jsx("h3", { className: "text-[17px] font-semibold tracking-tight text-black", children: directStaffName ? t("booking.modal.titleWithStaff", { name: directStaffName }) : t("booking.modal.titleDefault") }),
466
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: stepLabels.map(({ n, label }) => {
467
+ const active = step === n;
468
+ const complete = step > n;
469
+ const disabled = isTeam ? n >= 2 && !hasServices || n >= 3 && !selectedStaffMemberId || n >= 4 && (!hasServices || !selectedTime) : n >= 2 && !hasServices || n >= 3 && (!hasServices || !selectedTime);
470
+ return /* @__PURE__ */ jsx(
471
+ "button",
472
+ {
473
+ type: "button",
474
+ disabled,
475
+ onClick: () => goToStep(n),
476
+ className: `inline-flex h-7 min-w-[1.75rem] items-center justify-center rounded-full px-2.5 text-[11px] font-bold transition disabled:opacity-20 ${active || complete ? `text-white ${gradientCtaShadow}` : `bg-white text-black/50 ${cardShadow}`}`,
477
+ style: active || complete ? accentFillStyle : void 0,
478
+ title: label,
479
+ "aria-label": t("booking.modal.stepAria", { n, label }),
480
+ children: complete && !active ? "\u2713" : n
481
+ },
482
+ `header-step-${n}`
483
+ );
484
+ }) })
485
+ ] }),
486
+ /* @__PURE__ */ jsx(
487
+ "button",
488
+ {
489
+ type: "button",
490
+ className: `shrink-0 rounded-full bg-white p-2 text-black/40 transition active:bg-black/[0.03] ${cardShadow}`,
491
+ onClick: requestClose,
492
+ "aria-label": t("booking.modal.closeAria"),
493
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
494
+ }
495
+ )
496
+ ] }),
497
+ /* @__PURE__ */ jsx("div", { className: "relative z-[1] min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-contain bg-white px-4 py-5 sm:px-5", children: bookingSuccess ? bookingSuccessDetails ? /* @__PURE__ */ jsx(
498
+ BookingSuccessView,
499
+ {
500
+ serviceName: bookingSuccessDetails.serviceName,
501
+ dateLabel: bookingSuccessDetails.dateLabel,
502
+ time: bookingSuccessDetails.time,
503
+ salonName,
504
+ onClose
505
+ }
506
+ ) : /* @__PURE__ */ jsx("p", { className: "rounded-2xl bg-emerald-50 px-3.5 py-3 text-sm leading-relaxed text-emerald-700", children: bookingSuccess }) : /* @__PURE__ */ jsxs("form", { id: "salon-booking-form", onSubmit, className: "min-w-0 space-y-3.5 bg-white", children: [
507
+ step === 1 ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: hasServices && !browseAllServices ? /* @__PURE__ */ jsxs(Fragment, { children: [
508
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
509
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] font-semibold text-black", children: t("booking.modal.selectedServices") }),
510
+ /* @__PURE__ */ jsx("p", { className: "text-[12px] font-medium tabular-nums text-black/40", children: t(selectedServices.length === 1 ? "booking.modal.serviceCountOne" : "booking.modal.serviceCountMany", { count: selectedServices.length }) })
511
+ ] }),
512
+ selectedServiceIdxs.map((idx) => {
513
+ const svc = services[idx];
514
+ if (!svc) return null;
515
+ return /* @__PURE__ */ jsx(
516
+ "div",
517
+ {
518
+ className: `rounded-2xl p-px transition ${gradientRingShadow}`,
519
+ style: accentBorderStyle,
520
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 rounded-[15px] bg-white px-3.5 py-3.5", children: [
521
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
522
+ /* @__PURE__ */ jsx("p", { className: "truncate text-[16px] font-semibold text-black", children: svc.name }),
523
+ /* @__PURE__ */ jsx(ServiceDescription, { text: svc.description }),
524
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-[13px] tabular-nums text-black/70", children: [
525
+ svc.duration,
526
+ " ",
527
+ t("booking.modal.minSuffix"),
528
+ " \xB7 ",
529
+ fmtPrice(Number(svc.price ?? 0))
530
+ ] })
531
+ ] }),
532
+ /* @__PURE__ */ jsxs(
533
+ "button",
534
+ {
535
+ type: "button",
536
+ onClick: () => onToggleService(idx),
537
+ className: `mt-0.5 inline-flex shrink-0 items-center gap-1 rounded-full border border-black/10 bg-white px-2.5 py-1.5 text-xs font-semibold text-black/60 transition active:bg-black/[0.04] ${cardShadow}`,
538
+ "aria-label": t("booking.modal.removeAria"),
539
+ children: [
540
+ /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5", "aria-hidden": true }),
541
+ t("booking.modal.remove")
542
+ ]
543
+ }
544
+ )
545
+ ] })
546
+ },
547
+ `selected-${svc.id}-${idx}`
548
+ );
549
+ }),
550
+ /* @__PURE__ */ jsxs(
551
+ "button",
552
+ {
553
+ type: "button",
554
+ onClick: () => setBrowseAllServices(true),
555
+ className: `flex w-full items-center justify-center gap-1.5 rounded-2xl bg-white px-3.5 py-3 text-sm font-semibold text-black ${cardShadow}`,
556
+ children: [
557
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4", "aria-hidden": true }),
558
+ t("booking.modal.addMoreServices")
559
+ ]
560
+ }
561
+ )
562
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
563
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
564
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] font-semibold text-black", children: t("booking.modal.services") }),
565
+ /* @__PURE__ */ jsx("p", { className: "text-[12px] font-medium tabular-nums text-black/40", children: t(visibleCatalog.length === 1 ? "booking.modal.serviceCountOne" : "booking.modal.serviceCountMany", { count: visibleCatalog.length }) })
566
+ ] }),
567
+ hasServices ? /* @__PURE__ */ jsx(
568
+ "button",
569
+ {
570
+ type: "button",
571
+ onClick: () => setBrowseAllServices(false),
572
+ className: "text-[12px] font-semibold text-black underline underline-offset-2",
573
+ children: t("booking.modal.hideList", { count: selectedServices.length })
574
+ }
575
+ ) : null,
576
+ /* @__PURE__ */ jsx(
577
+ SalonServiceCategoryTabs,
578
+ {
579
+ categories: categoryTabs,
580
+ selectedId: selectedCategory,
581
+ onSelect: setSelectedCategory,
582
+ size: "sm",
583
+ className: "-mx-1 px-1",
584
+ accentFill
585
+ }
586
+ ),
587
+ visibleCatalog.map((service) => {
588
+ const variants = service.variants ?? [];
589
+ const variantLabel = variants.length > 0 ? selectedVariantByServiceId[service.id] ?? variants[0].label : null;
590
+ const active = isCatalogServiceSelected(
591
+ selectedServiceIdxs,
592
+ services,
593
+ service,
594
+ variantLabel
595
+ );
596
+ const { price, duration } = getCatalogDisplayPriceDuration(service, variantLabel);
597
+ return /* @__PURE__ */ jsx(
598
+ "div",
599
+ {
600
+ className: `rounded-2xl transition ${active ? `p-px ${gradientRingShadow}` : `bg-white ${cardShadow}`}`,
601
+ style: active ? accentBorderStyle : void 0,
602
+ children: /* @__PURE__ */ jsxs(
603
+ "div",
604
+ {
605
+ className: `flex items-start justify-between gap-3 ${active ? "rounded-[15px] bg-white px-3.5 py-3.5" : "px-3.5 py-3.5"}`,
606
+ children: [
607
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
608
+ /* @__PURE__ */ jsx("p", { className: "truncate text-[16px] font-semibold text-black", children: service.name }),
609
+ /* @__PURE__ */ jsx(ServiceDescription, { text: service.description }),
610
+ variants.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "relative mt-1.5 max-w-full", children: [
611
+ /* @__PURE__ */ jsxs(
612
+ "button",
613
+ {
614
+ type: "button",
615
+ onClick: () => setVariantDropdownOpenForServiceId(
616
+ (prev) => prev === service.id ? null : service.id
617
+ ),
618
+ className: "flex w-full items-center justify-between rounded-full border border-black/12 bg-white px-3 py-1.5 text-left text-xs transition hover:border-black/25",
619
+ children: [
620
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: variantLabel }),
621
+ /* @__PURE__ */ jsx(ChevronDown, { className: "ml-1 h-3.5 w-3.5 shrink-0 text-black/45", "aria-hidden": true })
622
+ ]
623
+ }
624
+ ),
625
+ variantDropdownOpenForServiceId === service.id ? /* @__PURE__ */ jsx("div", { className: "absolute left-0 right-0 z-20 mt-1 overflow-hidden rounded-xl border border-black/10 bg-white shadow-[0_8px_24px_rgba(0,0,0,0.12)]", children: variants.map((variant) => /* @__PURE__ */ jsxs(
626
+ "button",
627
+ {
628
+ type: "button",
629
+ onClick: () => {
630
+ setSelectedVariantByServiceId((prev) => ({
631
+ ...prev,
632
+ [service.id]: variant.label
633
+ }));
634
+ setVariantDropdownOpenForServiceId(null);
635
+ },
636
+ className: `w-full px-3 py-2 text-left text-xs hover:bg-black/[0.04] ${variantLabel === variant.label ? "font-semibold text-black" : "text-black/70"}`,
637
+ children: [
638
+ variant.label,
639
+ " \xB7 ",
640
+ fmtPrice(Number(variant.price) || 0)
641
+ ]
642
+ },
643
+ variant.label
644
+ )) }) : null
645
+ ] }) : null,
646
+ /* @__PURE__ */ jsxs("p", { className: "mt-1.5 text-[13px] tabular-nums text-black/45", children: [
647
+ duration,
648
+ " ",
649
+ t("booking.modal.minSuffix"),
650
+ " \xB7 ",
651
+ fmtPrice(Number(price) || 0)
652
+ ] })
653
+ ] }),
654
+ active ? /* @__PURE__ */ jsxs(
655
+ "button",
656
+ {
657
+ type: "button",
658
+ onClick: () => toggleCatalogService(service),
659
+ className: `mt-0.5 inline-flex shrink-0 items-center gap-1 rounded-full border border-black/10 bg-white px-2.5 py-1.5 text-xs font-semibold text-black/60 transition active:bg-black/[0.04] ${cardShadow}`,
660
+ "aria-label": t("booking.modal.removeAria"),
661
+ children: [
662
+ /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5", "aria-hidden": true }),
663
+ t("booking.modal.remove")
664
+ ]
665
+ }
666
+ ) : /* @__PURE__ */ jsxs(
667
+ "button",
668
+ {
669
+ type: "button",
670
+ onClick: () => toggleCatalogService(service),
671
+ className: `mt-0.5 inline-flex shrink-0 items-center gap-1 rounded-full px-2.5 py-1.5 text-xs font-semibold text-white transition ${gradientCtaShadow}`,
672
+ style: accentFillStyle,
673
+ children: [
674
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5", "aria-hidden": true }),
675
+ t("booking.modal.add")
676
+ ]
677
+ }
678
+ )
679
+ ]
680
+ }
681
+ )
682
+ },
683
+ service.id
684
+ );
685
+ }),
686
+ visibleCatalog.length === 0 ? /* @__PURE__ */ jsx("p", { className: `rounded-2xl bg-white px-3.5 py-3 text-sm text-black/40 ${cardShadow}`, children: t("booking.modal.noServicesInCategory") }) : null
687
+ ] }) }) : null,
688
+ step === 2 && isTeam ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
689
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] font-semibold text-black", children: t("booking.modal.selectSpecialist") }),
690
+ selectedServiceIds.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-[12px] text-black/45", children: [
691
+ t("booking.modal.availableFor"),
692
+ "\xA0",
693
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-black/70", children: selectedServiceIds.map((sid) => serviceCatalog.find((s) => s.id === sid)?.name ?? sid).join(" + ") })
694
+ ] }),
695
+ eligibleStaff.length === 0 ? /* @__PURE__ */ jsxs("div", { className: `rounded-2xl bg-white px-4 py-5 text-center ${cardShadow}`, children: [
696
+ /* @__PURE__ */ jsx("p", { className: "text-[14px] font-medium text-black/50", children: t("booking.modal.serviceUnavailable") }),
697
+ /* @__PURE__ */ jsx(
698
+ "button",
699
+ {
700
+ type: "button",
701
+ onClick: () => setStep(1),
702
+ className: "mt-3 text-[13px] font-semibold text-[color:var(--salon-primary)] underline underline-offset-2",
703
+ children: t("booking.modal.selectOtherService")
704
+ }
705
+ )
706
+ ] }) : /* @__PURE__ */ jsx("div", { className: "space-y-2.5", children: eligibleStaff.map((sm) => {
707
+ const selected = selectedStaffMemberId === sm.id;
708
+ return /* @__PURE__ */ jsxs(
709
+ "button",
710
+ {
711
+ type: "button",
712
+ onClick: () => onStaffMemberChange?.(sm.id),
713
+ className: `flex w-full items-center gap-3 rounded-2xl px-4 py-3.5 text-left transition ${selected ? `p-px ${gradientRingShadow}` : `bg-white ${cardShadow}`}`,
714
+ style: selected ? accentBorderStyle : void 0,
715
+ children: [
716
+ selected ? /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-white", style: accentFillStyle, children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4", "aria-hidden": true }) }) : /* @__PURE__ */ jsx("div", { className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-white text-black/30 ${cardShadow}`, children: /* @__PURE__ */ jsx(User, { className: "h-4 w-4", "aria-hidden": true }) }),
717
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
718
+ /* @__PURE__ */ jsx("p", { className: "text-[15px] font-semibold text-black", children: sm.name }),
719
+ sm.bio && /* @__PURE__ */ jsx("p", { className: "mt-0.5 truncate text-[12px] text-black/45", children: sm.bio })
720
+ ] })
721
+ ]
722
+ },
723
+ sm.id
724
+ );
725
+ }) })
726
+ ] }) : null,
727
+ isTeam && step === 3 || !isTeam && step === 2 ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
728
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [
729
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] font-semibold text-black", children: selectedStaffMemberId ? t("booking.modal.timeWithStaff", { name: staffMembers.find((s) => s.id === selectedStaffMemberId)?.name ?? "" }) : t("booking.modal.dateTime") }),
730
+ /* @__PURE__ */ jsxs(
731
+ "button",
732
+ {
733
+ type: "button",
734
+ onClick: () => setStep(1),
735
+ className: `inline-flex items-center gap-1 rounded-full bg-white px-3 py-1.5 text-xs font-semibold text-[color:var(--salon-primary)] ${cardShadow} active:bg-black/[0.03]`,
736
+ children: [
737
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
738
+ t("booking.modal.addService")
739
+ ]
740
+ }
741
+ )
742
+ ] }),
743
+ hasServices ? /* @__PURE__ */ jsx("p", { className: "text-[15px] font-semibold text-black", children: selectedServices.map((s) => s.name).join(" + ") }) : null,
744
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
745
+ /* @__PURE__ */ jsx("label", { className: "block text-[13px] font-semibold text-black", children: t("booking.modal.date") }),
746
+ /* @__PURE__ */ jsx("div", { className: "-mx-1 mt-2 flex gap-2.5 overflow-x-auto px-1 pb-1.5 scrollbar-none", children: dateOptions.map((d) => {
747
+ const active = selectedDate === d.iso;
748
+ return /* @__PURE__ */ jsxs(
749
+ "button",
750
+ {
751
+ type: "button",
752
+ onClick: () => onDateChange(d.iso),
753
+ className: `flex h-[4.25rem] w-[4.25rem] shrink-0 flex-col items-center justify-center rounded-2xl text-center transition ${active ? "text-white shadow-[0_4px_14px_rgba(0,0,0,0.18)]" : "border border-black/[0.06] bg-white text-black/60 shadow-[0_1px_4px_rgba(0,0,0,0.06),0_4px_12px_rgba(0,0,0,0.04)]"}`,
754
+ style: active ? { backgroundColor: "#000" } : void 0,
755
+ children: [
756
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium uppercase leading-none tabular-nums opacity-75", children: d.weekday }),
757
+ /* @__PURE__ */ jsx("span", { className: "mt-1 text-[12px] font-bold leading-tight tabular-nums", children: d.day })
758
+ ]
759
+ },
760
+ d.iso
761
+ );
762
+ }) })
763
+ ] }),
764
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
765
+ /* @__PURE__ */ jsx("label", { className: "block text-[13px] font-semibold text-black", children: t("booking.modal.time") }),
766
+ !selectedDate ? /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-sm text-black/35", children: t("booking.modal.selectDateFirst") }) : timeSlots === "closed" ? /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-sm text-black/35", children: t("booking.modal.dayClosed") }) : Array.isArray(timeSlots) && timeSlots.length === 0 ? /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-sm text-black/35", children: t("booking.modal.noSlots") }) : Array.isArray(timeSlots) ? /* @__PURE__ */ jsx("div", { className: "mt-2 grid w-full max-w-full grid-cols-3 gap-2.5 sm:grid-cols-4", children: timeSlots.map((t2) => {
767
+ const active = selectedTime === t2;
768
+ return /* @__PURE__ */ jsx(
769
+ "button",
770
+ {
771
+ type: "button",
772
+ onClick: () => onTimeChange(t2),
773
+ className: `min-w-0 touch-manipulation rounded-2xl px-2 py-3 text-center text-[14px] font-semibold tabular-nums transition ${active ? "text-white shadow-[0_4px_14px_rgba(0,0,0,0.22)]" : "border border-black/[0.08] bg-white text-black shadow-[0_2px_8px_rgba(0,0,0,0.12),0_4px_16px_rgba(0,0,0,0.08)] active:bg-black/[0.03] active:shadow-[0_1px_2px_rgba(0,0,0,0.10)]"}`,
774
+ style: active ? { backgroundColor: "#000" } : void 0,
775
+ children: t2
776
+ },
777
+ t2
778
+ );
779
+ }) }) : /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-sm text-black/35", children: t("booking.modal.selectServiceFirst") })
780
+ ] })
781
+ ] }) : null,
782
+ isTeam && step === 4 || !isTeam && step === 3 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3.5", children: [
783
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] font-semibold text-black", children: t("booking.modal.contactDetails") }),
784
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
785
+ /* @__PURE__ */ jsx("label", { className: "block text-[13px] font-semibold text-black", children: t("booking.modal.name") }),
786
+ /* @__PURE__ */ jsx(
787
+ "input",
788
+ {
789
+ className: fieldClass,
790
+ value: clientName,
791
+ onChange: (e) => onClientNameChange(e.target.value),
792
+ autoComplete: "name",
793
+ required: true
794
+ }
795
+ )
796
+ ] }),
797
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
798
+ /* @__PURE__ */ jsx("label", { className: "block text-[13px] font-semibold text-black", children: t("booking.modal.phone") }),
799
+ /* @__PURE__ */ jsx(
800
+ "input",
801
+ {
802
+ type: "tel",
803
+ className: fieldClass,
804
+ value: clientPhone,
805
+ onChange: (e) => onClientPhoneChange(e.target.value),
806
+ autoComplete: "tel",
807
+ inputMode: "tel",
808
+ required: true
809
+ }
810
+ )
811
+ ] }),
812
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
813
+ /* @__PURE__ */ jsx("label", { className: "block text-[13px] font-semibold text-black", children: t("booking.modal.email") }),
814
+ /* @__PURE__ */ jsx(
815
+ "input",
816
+ {
817
+ type: "email",
818
+ inputMode: "email",
819
+ autoComplete: "email",
820
+ className: fieldClass,
821
+ value: clientEmail,
822
+ onChange: (e) => onClientEmailChange(e.target.value),
823
+ required: true
824
+ }
825
+ )
826
+ ] }),
827
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
828
+ /* @__PURE__ */ jsx("label", { className: "block text-[13px] font-semibold text-black", children: t("booking.modal.notes") }),
829
+ /* @__PURE__ */ jsx(
830
+ "textarea",
831
+ {
832
+ className: `${fieldClass} resize-none`,
833
+ rows: 2,
834
+ value: notes,
835
+ onChange: (e) => onNotesChange(e.target.value)
836
+ }
837
+ )
838
+ ] })
839
+ ] }) : null,
840
+ bookingError ? /* @__PURE__ */ jsx("p", { className: "rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700", role: "alert", children: bookingError }) : null
841
+ ] }) }),
842
+ !bookingSuccess && !bookingSuccessDetails ? /* @__PURE__ */ jsxs("div", { className: "relative z-[2] shrink-0 border-t border-black/[0.06] bg-white px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3 sm:px-5", children: [
843
+ hasServices ? /* @__PURE__ */ jsxs("div", { className: "mb-3 px-1", children: [
844
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] tabular-nums text-black/50", children: t("booking.modal.totalDuration", { min: Math.max(0, totalDuration) }) }),
845
+ /* @__PURE__ */ jsx("p", { className: "text-[17px] font-semibold tabular-nums text-black/70 leading-tight", children: t("booking.modal.totalPrice", { price: fmtPrice(totalPrice) }) }),
846
+ selectedTime ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[13px] font-semibold tabular-nums text-black", children: t("booking.modal.startTime", { start: selectedTime, end: endTime }) }) : step > 1 ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 truncate text-[13px] font-semibold text-black", children: selectedServices.map((s) => s.name).join(" + ") }) : null
847
+ ] }) : null,
848
+ step === 3 && paymentType !== "none" && /* @__PURE__ */ jsxs("div", { className: "mb-2.5 space-y-1 text-center", children: [
849
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-1.5 text-[13px] font-semibold text-[#635BFF]", children: [
850
+ /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 60 60", fill: "none", "aria-hidden": true, children: [
851
+ /* @__PURE__ */ jsx("rect", { width: "60", height: "60", rx: "8", fill: "#635BFF" }),
852
+ /* @__PURE__ */ jsx("path", { d: "M27.5 22.5c0-1.7 1.4-2.4 3.6-2.4 3.2 0 7.3 1 10.4 2.7v-9.8c-3.5-1.4-7-2-10.4-2C23.1 11 18 15.2 18 22.9c0 12.1 16.6 10.2 16.6 15.4 0 2-1.7 2.7-4.1 2.7-3.5 0-8-1.5-11.5-3.5v9.9c3.9 1.7 7.9 2.4 11.5 2.4 8.8 0 14.8-4.3 14.8-12.2C45.3 25.4 27.5 27.6 27.5 22.5z", fill: "white" })
853
+ ] }),
854
+ paymentType === "deposit" && depositAmount && depositAmount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
855
+ t("booking.modal.depositRequired"),
856
+ " ",
857
+ /* @__PURE__ */ jsx("strong", { className: "mx-0.5", children: fmtPrice(Number(depositAmount) || 0) })
858
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
859
+ t("booking.modal.paymentFrom"),
860
+ " ",
861
+ /* @__PURE__ */ jsx("strong", { className: "mx-0.5", children: fmtPrice(totalPrice) })
862
+ ] })
863
+ ] }),
864
+ /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-black/35", children: [
865
+ t("booking.modal.securePayment"),
866
+ " ",
867
+ /* @__PURE__ */ jsx("span", { className: "font-bold text-[#635BFF]", children: "Stripe" })
868
+ ] }),
869
+ cancelPolicyHours ? /* @__PURE__ */ jsx("p", { className: "mx-auto max-w-[320px] text-[11px] leading-relaxed text-black/45", children: formatPolicySummary({
870
+ cancelPolicyHours,
871
+ cancelPolicyAction: cancelPolicyAction ?? "keep_deposit",
872
+ depositAmountEuros: depositAmount
873
+ }) }) : null
874
+ ] }),
875
+ (() => {
876
+ const maxStep = isTeam ? 4 : 3;
877
+ const isLastStep = step === maxStep;
878
+ const nextDisabled = step === 1 && !hasServices || isTeam && step === 2 && (!selectedStaffMemberId || eligibleStaff.length === 0) || (isTeam ? step === 3 : step === 2) && (!selectedDate || !selectedTime);
879
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
880
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2.5", children: [
881
+ /* @__PURE__ */ jsx(
882
+ "button",
883
+ {
884
+ type: "button",
885
+ onClick: () => setStep((s) => s > 1 ? s - 1 : s),
886
+ disabled: step === 1,
887
+ className: "rounded-full border border-black/10 bg-white py-2.5 text-[14px] font-medium text-black/60 transition disabled:opacity-25 active:scale-[0.98]",
888
+ children: t("booking.modal.back")
889
+ }
890
+ ),
891
+ !isLastStep ? /* @__PURE__ */ jsx(
892
+ "button",
893
+ {
894
+ type: "button",
895
+ onClick: () => {
896
+ if (!nextDisabled) setStep((s) => s < maxStep ? s + 1 : s);
897
+ },
898
+ disabled: nextDisabled,
899
+ className: `rounded-full py-3.5 text-[15px] font-semibold text-white transition disabled:opacity-40 ${gradientCtaShadow}`,
900
+ style: accentFillStyle,
901
+ children: t("booking.modal.continue")
902
+ }
903
+ ) : /* @__PURE__ */ jsxs(
904
+ "button",
905
+ {
906
+ type: "submit",
907
+ form: "salon-booking-form",
908
+ disabled: isSubmitting || !selectedTime || !hasServices,
909
+ className: `flex items-center justify-center gap-2 rounded-full py-3.5 text-[15px] font-semibold text-white transition disabled:opacity-40 ${gradientCtaShadow}`,
910
+ style: accentFillStyle,
911
+ children: [
912
+ isSubmitting ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }) : null,
913
+ paymentType !== "none" ? t("booking.modal.payAndBook") : t("booking.modal.sendRequest")
914
+ ]
915
+ }
916
+ )
917
+ ] }),
918
+ isLastStep ? /* @__PURE__ */ jsxs("p", { className: "mt-2.5 text-center text-[10.5px] leading-snug text-black/35", children: [
919
+ t("booking.modal.disclaimerPre"),
920
+ " ",
921
+ t("booking.modal.disclaimerAccept"),
922
+ " ",
923
+ /* @__PURE__ */ jsx("a", { href: termsHref, target: "_blank", rel: "noopener noreferrer", className: "underline underline-offset-2", children: t("booking.modal.terms") }),
924
+ " ",
925
+ t("booking.modal.smsConsentAnd"),
926
+ " ",
927
+ /* @__PURE__ */ jsx("a", { href: privacyHref, target: "_blank", rel: "noopener noreferrer", className: "underline underline-offset-2", children: t("booking.modal.privacy") }),
928
+ "."
929
+ ] }) : null
930
+ ] });
931
+ })()
932
+ ] }) : null
933
+ ]
934
+ }
935
+ )
936
+ ] });
937
+ }
938
+
939
+ export { SalonBookingModal };
940
+ //# sourceMappingURL=SalonBookingModal-ZIIKN2O2.js.map
941
+ //# sourceMappingURL=SalonBookingModal-ZIIKN2O2.js.map