@fayz-ai/plugin-agenda 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-DRZF6CV2.js +421 -0
- package/dist/chunk-DRZF6CV2.js.map +1 -0
- package/dist/chunk-LFUELUIT.cjs +423 -0
- package/dist/chunk-LFUELUIT.cjs.map +1 -0
- package/dist/index.cjs +741 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +741 -10
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/public/BookingWidget.d.ts +4 -2
- package/dist/public/BookingWidget.d.ts.map +1 -1
- package/dist/public/components/BrandHeader.d.ts +6 -0
- package/dist/public/components/BrandHeader.d.ts.map +1 -0
- package/dist/public/components/ServiceCard.d.ts +5 -0
- package/dist/public/components/ServiceCard.d.ts.map +1 -0
- package/dist/public/components/SidebarExtras.d.ts +4 -0
- package/dist/public/components/SidebarExtras.d.ts.map +1 -0
- package/dist/public/components/StepRailItem.d.ts +4 -0
- package/dist/public/components/StepRailItem.d.ts.map +1 -0
- package/dist/public/components/SuccessPanel.d.ts +4 -0
- package/dist/public/components/SuccessPanel.d.ts.map +1 -0
- package/dist/public/components/SummaryRail.d.ts +27 -0
- package/dist/public/components/SummaryRail.d.ts.map +1 -0
- package/dist/public/components/defaults.d.ts +3 -0
- package/dist/public/components/defaults.d.ts.map +1 -0
- package/dist/public/context.d.ts +22 -8
- package/dist/public/context.d.ts.map +1 -1
- package/dist/public/createPublicBookingPlugin.d.ts +27 -10
- package/dist/public/createPublicBookingPlugin.d.ts.map +1 -1
- package/dist/public/data.d.ts +37 -0
- package/dist/public/data.d.ts.map +1 -0
- package/dist/public/data.supabase.d.ts +7 -0
- package/dist/public/data.supabase.d.ts.map +1 -0
- package/dist/public/format.d.ts +18 -0
- package/dist/public/format.d.ts.map +1 -0
- package/dist/public/hooks.d.ts +13 -11
- package/dist/public/hooks.d.ts.map +1 -1
- package/dist/public/index.cjs +1016 -613
- package/dist/public/index.cjs.map +1 -1
- package/dist/public/index.d.ts +15 -4
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +1006 -616
- package/dist/public/index.js.map +1 -1
- package/dist/public/steps/ContactStep.d.ts +30 -0
- package/dist/public/steps/ContactStep.d.ts.map +1 -0
- package/dist/public/steps/DateTimeStep.d.ts +23 -0
- package/dist/public/steps/DateTimeStep.d.ts.map +1 -0
- package/dist/public/steps/OverviewStep.d.ts +16 -0
- package/dist/public/steps/OverviewStep.d.ts.map +1 -0
- package/dist/public/steps/PaymentStep.d.ts +20 -0
- package/dist/public/steps/PaymentStep.d.ts.map +1 -0
- package/dist/public/types.d.ts +134 -0
- package/dist/public/types.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/index.ts +11 -0
- package/src/migrations/001_public_booking.sql +278 -0
- package/src/migrations/index.ts +282 -0
- package/src/public/BookingWidget.tsx +136 -713
- package/src/public/components/BrandHeader.tsx +27 -0
- package/src/public/components/ServiceCard.tsx +39 -0
- package/src/public/components/SidebarExtras.tsx +30 -0
- package/src/public/components/StepRailItem.tsx +27 -0
- package/src/public/components/SuccessPanel.tsx +27 -0
- package/src/public/components/SummaryRail.tsx +90 -0
- package/src/public/components/defaults.ts +14 -0
- package/src/public/context.tsx +64 -7
- package/src/public/createPublicBookingPlugin.ts +131 -21
- package/src/public/data.supabase.ts +94 -0
- package/src/public/data.ts +99 -0
- package/src/public/format.ts +61 -0
- package/src/public/hooks.ts +22 -34
- package/src/public/index.ts +57 -4
- package/src/public/steps/ContactStep.tsx +179 -0
- package/src/public/steps/DateTimeStep.tsx +138 -0
- package/src/public/steps/OverviewStep.tsx +81 -0
- package/src/public/steps/PaymentStep.tsx +180 -0
- package/src/public/types.ts +169 -3
- package/dist/chunk-GAJVIEXM.cjs +0 -870
- package/dist/chunk-GAJVIEXM.cjs.map +0 -1
- package/dist/chunk-U6VTLOHN.js +0 -867
- package/dist/chunk-U6VTLOHN.js.map +0 -1
package/dist/public/index.cjs
CHANGED
|
@@ -1,14 +1,132 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('../chunk-SYDBDI3Q.cjs');
|
|
3
|
+
var chunkLFUELUIT_cjs = require('../chunk-LFUELUIT.cjs');
|
|
5
4
|
var react = require('react');
|
|
5
|
+
var lucideReact = require('lucide-react');
|
|
6
6
|
var core = require('@fayz-ai/core');
|
|
7
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
-
var lucideReact = require('lucide-react');
|
|
9
8
|
|
|
9
|
+
// src/public/data.ts
|
|
10
|
+
function createMockPublicBookingProvider(options) {
|
|
11
|
+
const inner = chunkLFUELUIT_cjs.createMockAgendaProvider({ seed: options.seed });
|
|
12
|
+
const { services, professionalId } = options;
|
|
13
|
+
return {
|
|
14
|
+
async getServices() {
|
|
15
|
+
return services;
|
|
16
|
+
},
|
|
17
|
+
async getAvailableSlots(query) {
|
|
18
|
+
return inner.getAvailableSlots({
|
|
19
|
+
assigneeId: query.assigneeId ?? professionalId,
|
|
20
|
+
date: query.date,
|
|
21
|
+
durationMinutes: query.durationMinutes,
|
|
22
|
+
slotInterval: query.slotInterval
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
async createBooking(input) {
|
|
26
|
+
const service = services.find((s) => s.id === input.serviceId);
|
|
27
|
+
if (!service) throw new Error("Servi\xE7o inv\xE1lido");
|
|
28
|
+
const booking = await inner.createBooking({
|
|
29
|
+
kind: "appointment",
|
|
30
|
+
// Mock: the customer is captured as the booking title; the Supabase
|
|
31
|
+
// path upserts a person record server-side instead.
|
|
32
|
+
clientId: "public-guest",
|
|
33
|
+
professionalId,
|
|
34
|
+
startsAt: input.startsAt,
|
|
35
|
+
title: input.contact.name,
|
|
36
|
+
notes: [input.contact.phone, input.contact.email, input.contact.notes].filter(Boolean).join(" \xB7 ") || void 0,
|
|
37
|
+
services: [
|
|
38
|
+
{
|
|
39
|
+
serviceId: service.id,
|
|
40
|
+
name: service.name,
|
|
41
|
+
durationMinutes: service.durationMinutes,
|
|
42
|
+
price: service.price,
|
|
43
|
+
assigneeId: professionalId
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
const endsAt = booking.endsAt ?? new Date(new Date(input.startsAt).getTime() + service.durationMinutes * 6e4).toISOString();
|
|
48
|
+
return { bookingId: booking.id, startsAt: booking.startsAt, endsAt };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function getClient() {
|
|
53
|
+
const client = core.getSupabaseClientOptional();
|
|
54
|
+
if (!client) throw new Error("[plugin-agenda/public] Supabase client not initialized");
|
|
55
|
+
return client;
|
|
56
|
+
}
|
|
57
|
+
function toPhoneDigits(phone) {
|
|
58
|
+
return (phone ?? "").replace(/\D/g, "");
|
|
59
|
+
}
|
|
60
|
+
function createSupabasePublicBookingProvider(options) {
|
|
61
|
+
const { tenantId } = options;
|
|
62
|
+
return {
|
|
63
|
+
async getServices() {
|
|
64
|
+
const { data, error } = await getClient().from("v_public_services").select("id, name, description, price, currency, duration_minutes, sort_order").eq("tenant_id", tenantId).order("sort_order", { ascending: true });
|
|
65
|
+
if (error) throw new Error(`[plugin-agenda/public] getServices: ${error.message}`);
|
|
66
|
+
return (data ?? []).map((row) => ({
|
|
67
|
+
id: String(row.id),
|
|
68
|
+
name: String(row.name),
|
|
69
|
+
description: row.description ?? void 0,
|
|
70
|
+
price: Number(row.price ?? 0),
|
|
71
|
+
durationMinutes: Number(row.duration_minutes ?? 30)
|
|
72
|
+
}));
|
|
73
|
+
},
|
|
74
|
+
async getAvailableSlots(query) {
|
|
75
|
+
const { data, error } = await getClient().rpc("get_available_slots", {
|
|
76
|
+
p_tenant_id: tenantId,
|
|
77
|
+
p_assignee_id: query.assigneeId,
|
|
78
|
+
p_date: query.date,
|
|
79
|
+
p_duration_minutes: query.durationMinutes,
|
|
80
|
+
p_slot_interval: query.slotInterval
|
|
81
|
+
});
|
|
82
|
+
if (error) throw new Error(`[plugin-agenda/public] getAvailableSlots: ${error.message}`);
|
|
83
|
+
return (data ?? []).map((row) => ({
|
|
84
|
+
start: row.slot_start,
|
|
85
|
+
end: row.slot_end
|
|
86
|
+
}));
|
|
87
|
+
},
|
|
88
|
+
async createBooking(input) {
|
|
89
|
+
const phone = toPhoneDigits(input.contact.phone);
|
|
90
|
+
const { data, error } = await getClient().rpc("create_public_booking", {
|
|
91
|
+
p_tenant_id: tenantId,
|
|
92
|
+
p_service_id: input.serviceId,
|
|
93
|
+
p_starts_at: input.startsAt,
|
|
94
|
+
p_name: input.contact.name.trim(),
|
|
95
|
+
p_phone: phone,
|
|
96
|
+
p_email: input.contact.email?.trim() || null,
|
|
97
|
+
p_notes: input.contact.notes?.trim() || null
|
|
98
|
+
});
|
|
99
|
+
if (error) throw new Error(`[plugin-agenda/public] createBooking: ${error.message}`);
|
|
100
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
101
|
+
if (!row) throw new Error("[plugin-agenda/public] createBooking: empty RPC response");
|
|
102
|
+
return { bookingId: row.booking_id, startsAt: row.starts_at, endsAt: row.ends_at };
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
10
106
|
var PublicBookingContext = react.createContext(null);
|
|
11
|
-
function BookingProvider({
|
|
107
|
+
function BookingProvider({ base, staticServices, children }) {
|
|
108
|
+
const [fetched, setFetched] = react.useState(null);
|
|
109
|
+
const [loading, setLoading] = react.useState(staticServices === null);
|
|
110
|
+
react.useEffect(() => {
|
|
111
|
+
if (staticServices !== null) return;
|
|
112
|
+
let active = true;
|
|
113
|
+
setLoading(true);
|
|
114
|
+
base.provider.getServices().then((result) => {
|
|
115
|
+
if (active) setFetched(result);
|
|
116
|
+
}).catch((err) => {
|
|
117
|
+
console.error("[plugin-agenda/public] failed to load services:", err);
|
|
118
|
+
if (active) setFetched([]);
|
|
119
|
+
}).finally(() => {
|
|
120
|
+
if (active) setLoading(false);
|
|
121
|
+
});
|
|
122
|
+
return () => {
|
|
123
|
+
active = false;
|
|
124
|
+
};
|
|
125
|
+
}, [base.provider, staticServices]);
|
|
126
|
+
const value = react.useMemo(
|
|
127
|
+
() => ({ ...base, services: staticServices ?? fetched ?? [], servicesLoading: loading }),
|
|
128
|
+
[base, staticServices, fetched, loading]
|
|
129
|
+
);
|
|
12
130
|
return /* @__PURE__ */ jsxRuntime.jsx(PublicBookingContext.Provider, { value, children });
|
|
13
131
|
}
|
|
14
132
|
function useBooking() {
|
|
@@ -21,6 +139,10 @@ function useBooking() {
|
|
|
21
139
|
function useServices() {
|
|
22
140
|
return useBooking().services;
|
|
23
141
|
}
|
|
142
|
+
function usePublicServices() {
|
|
143
|
+
const { services, servicesLoading } = useBooking();
|
|
144
|
+
return { services, loading: servicesLoading };
|
|
145
|
+
}
|
|
24
146
|
function useAvailableSlots(params) {
|
|
25
147
|
const { provider, professionalId, services, window: window2 } = useBooking();
|
|
26
148
|
const [slots, setSlots] = react.useState([]);
|
|
@@ -53,36 +175,17 @@ function useAvailableSlots(params) {
|
|
|
53
175
|
return { slots, loading, error };
|
|
54
176
|
}
|
|
55
177
|
function useCreateBooking() {
|
|
56
|
-
const { provider
|
|
178
|
+
const { provider } = useBooking();
|
|
57
179
|
const [submitting, setSubmitting] = react.useState(false);
|
|
58
180
|
const [error, setError] = react.useState(null);
|
|
59
181
|
const [done, setDone] = react.useState(false);
|
|
60
182
|
async function submit(input) {
|
|
61
|
-
const service = services.find((s) => s.id === input.serviceId);
|
|
62
|
-
if (!service) throw new Error("Servi\xE7o inv\xE1lido");
|
|
63
183
|
setSubmitting(true);
|
|
64
184
|
setError(null);
|
|
65
185
|
try {
|
|
66
|
-
await provider.createBooking(
|
|
67
|
-
kind: "appointment",
|
|
68
|
-
// POC: the customer is captured as the booking title; a real flow would
|
|
69
|
-
// upsert a client/person record and pass its id here.
|
|
70
|
-
clientId: "public-guest",
|
|
71
|
-
professionalId,
|
|
72
|
-
startsAt: input.startsAt,
|
|
73
|
-
title: input.contact.name,
|
|
74
|
-
notes: [input.contact.phone, input.contact.email, input.contact.notes].filter(Boolean).join(" \xB7 ") || void 0,
|
|
75
|
-
services: [
|
|
76
|
-
{
|
|
77
|
-
serviceId: service.id,
|
|
78
|
-
name: service.name,
|
|
79
|
-
durationMinutes: service.durationMinutes,
|
|
80
|
-
price: service.price,
|
|
81
|
-
assigneeId: professionalId
|
|
82
|
-
}
|
|
83
|
-
]
|
|
84
|
-
});
|
|
186
|
+
const result = await provider.createBooking(input);
|
|
85
187
|
setDone(true);
|
|
188
|
+
return result;
|
|
86
189
|
} catch (err) {
|
|
87
190
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
88
191
|
setError(e);
|
|
@@ -131,7 +234,368 @@ function formatSlotRange(startIso, durationMin, fmt) {
|
|
|
131
234
|
const end = new Date(start.getTime() + durationMin * 6e4);
|
|
132
235
|
return `${fmt.dateShort.format(start)} \u2013 ${fmt.time.format(start)} at\xE9 ${fmt.time.format(end)} (${durationMin}min)`;
|
|
133
236
|
}
|
|
134
|
-
|
|
237
|
+
function OverviewStep({
|
|
238
|
+
services,
|
|
239
|
+
servicesLoading,
|
|
240
|
+
businessHours,
|
|
241
|
+
professionalName,
|
|
242
|
+
labels,
|
|
243
|
+
brand,
|
|
244
|
+
fmt,
|
|
245
|
+
components: C,
|
|
246
|
+
onStartBooking
|
|
247
|
+
}) {
|
|
248
|
+
const openDays = new Set(businessHours.daysOfWeek);
|
|
249
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_18rem]", children: [
|
|
250
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
|
|
251
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "rounded-2xl border border-border bg-card p-6 shadow-sm", children: [
|
|
252
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-4 font-heading text-lg font-bold text-foreground", children: labels.servicesHeading }),
|
|
253
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: servicesLoading ? Array.from({ length: 2 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-24 animate-pulse rounded-2xl bg-muted" }, i)) : services.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
254
|
+
C.ServiceCard,
|
|
255
|
+
{
|
|
256
|
+
service: s,
|
|
257
|
+
featured: i === 0,
|
|
258
|
+
onSelect: () => onStartBooking(s.id),
|
|
259
|
+
fmt,
|
|
260
|
+
brand
|
|
261
|
+
},
|
|
262
|
+
s.id
|
|
263
|
+
)) })
|
|
264
|
+
] }),
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "rounded-2xl border border-border bg-card p-6 shadow-sm", children: [
|
|
266
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-lg font-bold text-foreground", children: labels.aboutHeading }),
|
|
267
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-3 mb-2 flex items-center gap-1.5 text-sm font-medium text-muted-foreground", children: [
|
|
268
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4" }),
|
|
269
|
+
" ",
|
|
270
|
+
labels.openingHoursHeading
|
|
271
|
+
] }),
|
|
272
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-border border-l-2 border-primary/20 pl-4", children: WEEK_ORDER.map((day) => {
|
|
273
|
+
const open = openDays.has(day);
|
|
274
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between py-2 text-sm", children: [
|
|
275
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: DAY_NAMES[day] }),
|
|
276
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: open ? "tabular-nums text-foreground" : "text-muted-foreground", children: open ? `${businessHours.start} \u2013 ${businessHours.end}` : labels.closedLabel })
|
|
277
|
+
] }, day);
|
|
278
|
+
}) })
|
|
279
|
+
] })
|
|
280
|
+
] }),
|
|
281
|
+
/* @__PURE__ */ jsxRuntime.jsx("aside", { className: "lg:sticky lg:top-24 lg:self-start", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
282
|
+
C.SidebarExtras,
|
|
283
|
+
{
|
|
284
|
+
professionalName,
|
|
285
|
+
brand,
|
|
286
|
+
labels,
|
|
287
|
+
onStartBooking: () => onStartBooking()
|
|
288
|
+
}
|
|
289
|
+
) })
|
|
290
|
+
] });
|
|
291
|
+
}
|
|
292
|
+
function DateTimeStep({
|
|
293
|
+
labels,
|
|
294
|
+
fmt,
|
|
295
|
+
week,
|
|
296
|
+
weekOffset,
|
|
297
|
+
maxWeekOffset,
|
|
298
|
+
onWeekOffset,
|
|
299
|
+
businessHours,
|
|
300
|
+
date,
|
|
301
|
+
onSelectDate,
|
|
302
|
+
slots,
|
|
303
|
+
slotsLoading,
|
|
304
|
+
slotStart,
|
|
305
|
+
onSelectSlot,
|
|
306
|
+
onContinue
|
|
307
|
+
}) {
|
|
308
|
+
const openDays = new Set(businessHours.daysOfWeek);
|
|
309
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
310
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [
|
|
311
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-xl font-bold text-foreground", children: labels.dateStep }),
|
|
312
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3.5 w-3.5" }),
|
|
314
|
+
" ",
|
|
315
|
+
labels.timezoneNote
|
|
316
|
+
] })
|
|
317
|
+
] }),
|
|
318
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex items-center justify-between", children: [
|
|
319
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold capitalize text-foreground", children: fmt.month.format(week[0].date) }),
|
|
320
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
321
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
322
|
+
"button",
|
|
323
|
+
{
|
|
324
|
+
type: "button",
|
|
325
|
+
disabled: weekOffset === 0,
|
|
326
|
+
onClick: () => onWeekOffset((w) => Math.max(0, w - 1)),
|
|
327
|
+
className: "flex h-9 w-9 items-center justify-center rounded-lg border border-border text-foreground transition-colors hover:bg-accent disabled:opacity-40 disabled:hover:bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
328
|
+
"aria-label": labels.prevWeek,
|
|
329
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
|
|
330
|
+
}
|
|
331
|
+
),
|
|
332
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
333
|
+
"button",
|
|
334
|
+
{
|
|
335
|
+
type: "button",
|
|
336
|
+
disabled: weekOffset >= maxWeekOffset,
|
|
337
|
+
onClick: () => onWeekOffset((w) => Math.min(maxWeekOffset, w + 1)),
|
|
338
|
+
className: "flex h-9 w-9 items-center justify-center rounded-lg border border-border text-foreground transition-colors hover:bg-accent disabled:opacity-40 disabled:hover:bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
339
|
+
"aria-label": labels.nextWeek,
|
|
340
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
] })
|
|
344
|
+
] }),
|
|
345
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 grid grid-cols-7 gap-1.5", children: week.map((d) => {
|
|
346
|
+
const selected = date === d.iso;
|
|
347
|
+
const closed = !openDays.has(d.date.getDay());
|
|
348
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
349
|
+
"button",
|
|
350
|
+
{
|
|
351
|
+
type: "button",
|
|
352
|
+
disabled: closed,
|
|
353
|
+
onClick: () => onSelectDate(d.iso),
|
|
354
|
+
className: [
|
|
355
|
+
"flex flex-col items-center gap-0.5 rounded-xl border py-2.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
356
|
+
closed ? "cursor-not-allowed border-transparent text-muted-foreground/40" : selected ? "border-primary bg-primary text-primary-foreground" : "border-border text-foreground hover:border-primary/60 hover:bg-accent/40"
|
|
357
|
+
].join(" "),
|
|
358
|
+
children: [
|
|
359
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium capitalize opacity-80", children: fmt.weekday.format(d.date).replace(".", "") }),
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold tabular-nums", children: fmt.dayNum.format(d.date) })
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
d.iso
|
|
364
|
+
);
|
|
365
|
+
}) }),
|
|
366
|
+
date ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6", children: [
|
|
367
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-sm font-semibold text-foreground", children: labels.slotsHeading }),
|
|
368
|
+
slotsLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-4", children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 animate-pulse rounded-lg bg-muted" }, i)) }) : slots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-dashed border-border p-6 text-center text-sm text-muted-foreground", children: labels.noSlots }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-4", children: slots.map((s) => {
|
|
369
|
+
const selected = slotStart === s.start;
|
|
370
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
371
|
+
"button",
|
|
372
|
+
{
|
|
373
|
+
type: "button",
|
|
374
|
+
onClick: () => onSelectSlot(s.start),
|
|
375
|
+
className: [
|
|
376
|
+
"rounded-lg border py-2.5 text-sm font-semibold tabular-nums transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
377
|
+
selected ? "border-primary bg-primary text-primary-foreground" : "border-border text-foreground hover:border-primary/60 hover:bg-accent/40"
|
|
378
|
+
].join(" "),
|
|
379
|
+
children: fmt.time.format(new Date(s.start))
|
|
380
|
+
},
|
|
381
|
+
s.start
|
|
382
|
+
);
|
|
383
|
+
}) })
|
|
384
|
+
] }) : null,
|
|
385
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
386
|
+
"button",
|
|
387
|
+
{
|
|
388
|
+
type: "button",
|
|
389
|
+
disabled: !slotStart,
|
|
390
|
+
onClick: onContinue,
|
|
391
|
+
className: "mt-6 w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
392
|
+
children: labels.continueCta
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
] });
|
|
396
|
+
}
|
|
397
|
+
function ContactStep({
|
|
398
|
+
labels,
|
|
399
|
+
contact,
|
|
400
|
+
onContactChange,
|
|
401
|
+
contactStep,
|
|
402
|
+
country,
|
|
403
|
+
onCountryChange,
|
|
404
|
+
code,
|
|
405
|
+
onCodeChange,
|
|
406
|
+
codeError,
|
|
407
|
+
onSendCode,
|
|
408
|
+
onConfirmCode,
|
|
409
|
+
onBackToPhone,
|
|
410
|
+
onSubmitDetails,
|
|
411
|
+
payment,
|
|
412
|
+
submitting,
|
|
413
|
+
error
|
|
414
|
+
}) {
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-xl font-bold text-foreground", children: labels.contactStep }),
|
|
417
|
+
contactStep === "phone" ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => {
|
|
418
|
+
e.preventDefault();
|
|
419
|
+
onSendCode();
|
|
420
|
+
}, children: [
|
|
421
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.contactIntro }),
|
|
422
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5", children: [
|
|
423
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-phone", className: "mb-1 block text-sm font-medium text-foreground", children: labels.phoneLabel }),
|
|
424
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
|
|
425
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
427
|
+
"select",
|
|
428
|
+
{
|
|
429
|
+
"aria-label": "Pa\xEDs",
|
|
430
|
+
value: country.iso2,
|
|
431
|
+
onChange: (e) => onCountryChange(e.target.value),
|
|
432
|
+
className: "h-full appearance-none rounded-l-xl border border-r-0 border-border bg-muted py-3 pl-3 pr-8 text-sm font-medium text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
433
|
+
children: core.COUNTRIES.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.iso2, children: [
|
|
434
|
+
c.flag,
|
|
435
|
+
" ",
|
|
436
|
+
c.dial
|
|
437
|
+
] }, c.iso2))
|
|
438
|
+
}
|
|
439
|
+
),
|
|
440
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" })
|
|
441
|
+
] }),
|
|
442
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
443
|
+
"input",
|
|
444
|
+
{
|
|
445
|
+
id: "bk-phone",
|
|
446
|
+
type: "tel",
|
|
447
|
+
autoComplete: "tel",
|
|
448
|
+
inputMode: "tel",
|
|
449
|
+
value: contact.phone,
|
|
450
|
+
onChange: (e) => onContactChange({ ...contact, phone: core.maskPhone(e.target.value, country.mask) }),
|
|
451
|
+
className: "w-full rounded-r-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
452
|
+
placeholder: `Ex.: ${core.maskPhone("11987654321", country.mask)}`
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
] }),
|
|
456
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: labels.phoneHint })
|
|
457
|
+
] }),
|
|
458
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
459
|
+
"button",
|
|
460
|
+
{
|
|
461
|
+
type: "submit",
|
|
462
|
+
disabled: core.unmaskPhone(contact.phone).length < 8,
|
|
463
|
+
className: "mt-5 w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
464
|
+
children: labels.sendCodeCta
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
] }) : null,
|
|
468
|
+
contactStep === "details" ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => {
|
|
469
|
+
e.preventDefault();
|
|
470
|
+
onSubmitDetails();
|
|
471
|
+
}, children: [
|
|
472
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.contactIntro }),
|
|
473
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex items-center justify-between rounded-xl border border-border bg-muted/40 px-4 py-3", children: [
|
|
474
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 text-sm text-foreground", children: [
|
|
475
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "h-4 w-4 text-primary" }),
|
|
476
|
+
" ",
|
|
477
|
+
labels.phoneVerified,
|
|
478
|
+
" \xB7 ",
|
|
479
|
+
country.flag,
|
|
480
|
+
" ",
|
|
481
|
+
country.dial,
|
|
482
|
+
" ",
|
|
483
|
+
contact.phone
|
|
484
|
+
] }),
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
486
|
+
"button",
|
|
487
|
+
{
|
|
488
|
+
type: "button",
|
|
489
|
+
onClick: onBackToPhone,
|
|
490
|
+
className: "rounded text-sm font-medium text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
491
|
+
children: labels.changePhone
|
|
492
|
+
}
|
|
493
|
+
)
|
|
494
|
+
] }),
|
|
495
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 space-y-3", children: [
|
|
496
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
497
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-name", className: "mb-1 block text-sm font-medium text-foreground", children: labels.nameLabel }),
|
|
498
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
499
|
+
"input",
|
|
500
|
+
{
|
|
501
|
+
id: "bk-name",
|
|
502
|
+
type: "text",
|
|
503
|
+
autoComplete: "name",
|
|
504
|
+
value: contact.name,
|
|
505
|
+
onChange: (e) => onContactChange({ ...contact, name: e.target.value }),
|
|
506
|
+
className: "w-full rounded-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
507
|
+
placeholder: labels.namePlaceholder
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
] }),
|
|
511
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
512
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-email", className: "mb-1 block text-sm font-medium text-foreground", children: labels.emailLabel }),
|
|
513
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
514
|
+
"input",
|
|
515
|
+
{
|
|
516
|
+
id: "bk-email",
|
|
517
|
+
type: "email",
|
|
518
|
+
autoComplete: "email",
|
|
519
|
+
inputMode: "email",
|
|
520
|
+
value: contact.email,
|
|
521
|
+
onChange: (e) => onContactChange({ ...contact, email: e.target.value }),
|
|
522
|
+
className: "w-full rounded-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
523
|
+
placeholder: labels.emailPlaceholder
|
|
524
|
+
}
|
|
525
|
+
),
|
|
526
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.emailHint })
|
|
527
|
+
] }),
|
|
528
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
529
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-notes", className: "mb-1 block text-sm font-medium text-foreground", children: labels.notesLabel }),
|
|
530
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
531
|
+
"textarea",
|
|
532
|
+
{
|
|
533
|
+
id: "bk-notes",
|
|
534
|
+
rows: 3,
|
|
535
|
+
value: contact.notes,
|
|
536
|
+
onChange: (e) => onContactChange({ ...contact, notes: e.target.value }),
|
|
537
|
+
className: "w-full resize-none rounded-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
538
|
+
placeholder: labels.notesPlaceholder
|
|
539
|
+
}
|
|
540
|
+
)
|
|
541
|
+
] })
|
|
542
|
+
] }),
|
|
543
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-3 text-sm text-destructive", children: labels.submitError }) : null,
|
|
544
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
545
|
+
"button",
|
|
546
|
+
{
|
|
547
|
+
type: "submit",
|
|
548
|
+
disabled: !contact.name.trim() || submitting,
|
|
549
|
+
className: "mt-5 flex w-full items-center justify-center gap-2 rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
550
|
+
children: [
|
|
551
|
+
submitting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }),
|
|
552
|
+
payment ? labels.saveAndContinueCta : labels.confirmCta
|
|
553
|
+
]
|
|
554
|
+
}
|
|
555
|
+
),
|
|
556
|
+
!payment ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-3 flex items-center justify-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
557
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-3.5 w-3.5" }),
|
|
558
|
+
" ",
|
|
559
|
+
labels.noChargeNote
|
|
560
|
+
] }) : null
|
|
561
|
+
] }) : null,
|
|
562
|
+
contactStep === "code" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-foreground/40 p-4", role: "dialog", "aria-modal": "true", "aria-label": labels.codeTitle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-6 shadow-xl", children: [
|
|
563
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-heading text-lg font-bold text-foreground", children: labels.codeTitle }),
|
|
564
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.codeBody }),
|
|
565
|
+
/* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => {
|
|
566
|
+
e.preventDefault();
|
|
567
|
+
onConfirmCode();
|
|
568
|
+
}, children: [
|
|
569
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
570
|
+
"input",
|
|
571
|
+
{
|
|
572
|
+
type: "text",
|
|
573
|
+
inputMode: "numeric",
|
|
574
|
+
value: code,
|
|
575
|
+
autoFocus: true,
|
|
576
|
+
onChange: (e) => onCodeChange(e.target.value),
|
|
577
|
+
className: "mt-4 w-full rounded-xl border border-border bg-background px-4 py-3 text-center text-lg tracking-[0.4em] text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring placeholder:text-sm placeholder:tracking-normal",
|
|
578
|
+
placeholder: "Digite o c\xF3digo"
|
|
579
|
+
}
|
|
580
|
+
),
|
|
581
|
+
codeError ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-2 text-sm text-destructive", children: labels.codeInvalid }) : null,
|
|
582
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
583
|
+
"button",
|
|
584
|
+
{
|
|
585
|
+
type: "submit",
|
|
586
|
+
className: "mt-4 w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
587
|
+
children: labels.codeConfirmCta
|
|
588
|
+
}
|
|
589
|
+
)
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
592
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.codeResendHint }),
|
|
593
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onBackToPhone, className: "font-medium text-primary hover:underline", children: labels.back })
|
|
594
|
+
] })
|
|
595
|
+
] }) }) : null
|
|
596
|
+
] });
|
|
597
|
+
}
|
|
598
|
+
var PIX_PAYLOAD = "00020126840014br.gov.bcb.pix2562qrcode.fayz.ai/pix/v2/9d8f7a6b-agendamento5204000053039865802BR5904FAYZ6009SAO PAULO62070503***6304AB12";
|
|
135
599
|
function QrPlaceholder({ data }) {
|
|
136
600
|
const size = 25;
|
|
137
601
|
let seed = 0;
|
|
@@ -144,9 +608,9 @@ function QrPlaceholder({ data }) {
|
|
|
144
608
|
for (let y = 0; y < size; y++) {
|
|
145
609
|
for (let x = 0; x < size; x++) {
|
|
146
610
|
if (isFinder(x, y)) {
|
|
147
|
-
const local = (bx, by) => ({ lx: x - bx, ly: y - by });
|
|
148
611
|
const corner = x < 7 && y < 7 ? { bx: 0, by: 0 } : x >= size - 7 && y < 7 ? { bx: size - 7, by: 0 } : { bx: 0, by: size - 7 };
|
|
149
|
-
const
|
|
612
|
+
const lx = x - corner.bx;
|
|
613
|
+
const ly = y - corner.by;
|
|
150
614
|
const outer = lx === 0 || lx === 6 || ly === 0 || ly === 6;
|
|
151
615
|
const inner = lx >= 2 && lx <= 4 && ly >= 2 && ly <= 4;
|
|
152
616
|
if (outer || inner) cells.push({ x, y });
|
|
@@ -160,84 +624,197 @@ function QrPlaceholder({ data }) {
|
|
|
160
624
|
cells.map((c, i) => /* @__PURE__ */ jsxRuntime.jsx("rect", { x: c.x, y: c.y, width: 1, height: 1, fill: "currentColor", className: "text-foreground" }, i))
|
|
161
625
|
] });
|
|
162
626
|
}
|
|
163
|
-
function
|
|
164
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-5 flex items-center gap-3 rounded-2xl border border-border bg-card px-5 py-4 shadow-sm", children: [
|
|
165
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-11 w-11 items-center justify-center rounded-xl bg-primary text-lg font-bold text-primary-foreground", children: "H" }),
|
|
166
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
167
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-heading text-base font-bold text-foreground", children: "HempDent \xB7 Consulta Can\xE1bica" }),
|
|
168
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
169
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "h-3.5 w-3.5 text-primary" }),
|
|
170
|
-
" Agendamento online seguro \xB7 CRO-SP 166513"
|
|
171
|
-
] })
|
|
172
|
-
] })
|
|
173
|
-
] });
|
|
174
|
-
}
|
|
175
|
-
function ServiceRow({
|
|
627
|
+
function PaymentStep({
|
|
176
628
|
service,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
629
|
+
payment,
|
|
630
|
+
paymentStep,
|
|
631
|
+
charge,
|
|
632
|
+
busy,
|
|
633
|
+
error,
|
|
634
|
+
copied,
|
|
635
|
+
fmt,
|
|
636
|
+
onPayWithPix,
|
|
637
|
+
onCopy,
|
|
638
|
+
onSimulatePaid
|
|
180
639
|
}) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
640
|
+
const feePct = payment.reservationFeePercent;
|
|
641
|
+
const fee = Math.round(service.price * feePct) / 100;
|
|
642
|
+
const remaining = Math.round((service.price - fee) * 100) / 100;
|
|
643
|
+
if (paymentStep === "choose") {
|
|
644
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
645
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl border border-border p-4", children: [
|
|
646
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between font-semibold text-foreground", children: [
|
|
647
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: service.name }),
|
|
648
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: fmt.price.format(service.price) })
|
|
649
|
+
] }),
|
|
650
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-2 text-sm", children: [
|
|
651
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-foreground", children: [
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
653
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-4 w-4 items-center justify-center rounded bg-primary text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3" }) }),
|
|
654
|
+
"Taxa de reserva ",
|
|
655
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground", children: [
|
|
656
|
+
feePct,
|
|
657
|
+
"%"
|
|
658
|
+
] })
|
|
659
|
+
] }),
|
|
660
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: fmt.price.format(fee) })
|
|
193
661
|
] }),
|
|
194
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
195
|
-
/* @__PURE__ */ jsxRuntime.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
662
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-muted-foreground", children: [
|
|
663
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
664
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-4 w-4 rounded border border-border" }),
|
|
665
|
+
" Valor restante"
|
|
666
|
+
] }),
|
|
667
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: remaining <= 0 ? "Gr\xE1tis" : fmt.price.format(remaining) })
|
|
199
668
|
] }),
|
|
200
|
-
|
|
669
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "flex items-center gap-1 text-sm font-medium text-primary hover:underline", children: [
|
|
670
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
|
|
671
|
+
" Adicionar cupom"
|
|
672
|
+
] })
|
|
201
673
|
] }),
|
|
202
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
203
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", {
|
|
204
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
674
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-between border-t border-dashed border-border pt-3 font-semibold text-foreground", children: [
|
|
675
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Valor total" }),
|
|
676
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: fmt.price.format(service.price) })
|
|
205
677
|
] })
|
|
206
|
-
]
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
678
|
+
] }),
|
|
679
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex items-center justify-between", children: [
|
|
680
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-lg font-bold text-foreground", children: "Escolha como pagar" }),
|
|
681
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
682
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "h-3.5 w-3.5 text-primary" }),
|
|
683
|
+
" Pagamento seguro"
|
|
684
|
+
] })
|
|
685
|
+
] }),
|
|
686
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 rounded-2xl border-2 border-primary bg-accent/20 p-4", children: [
|
|
687
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2 font-semibold text-foreground", children: [
|
|
688
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }) }),
|
|
689
|
+
"Pix"
|
|
690
|
+
] }),
|
|
691
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3", children: [
|
|
692
|
+
{ icon: lucideReact.Check, t: 'Finalize o agendamento clicando em "Pagar com Pix".' },
|
|
693
|
+
{ icon: lucideReact.QrCode, t: "Mostraremos um QR Code e o Pix copia-e-cola." },
|
|
694
|
+
{ icon: lucideReact.CalendarCheck, t: "Ap\xF3s o pagamento, seu agendamento \xE9 reservado." }
|
|
695
|
+
].map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-muted-foreground", children: [
|
|
696
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-2 flex h-8 w-8 items-center justify-center rounded-lg bg-accent text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(s.icon, { className: "h-4 w-4" }) }),
|
|
697
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-foreground", children: [
|
|
698
|
+
i + 1,
|
|
699
|
+
"."
|
|
700
|
+
] }),
|
|
701
|
+
" ",
|
|
702
|
+
s.t
|
|
703
|
+
] }, i)) })
|
|
704
|
+
] }),
|
|
705
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-3 text-sm text-destructive", children: "N\xE3o foi poss\xEDvel iniciar o pagamento. Tente novamente." }) : null,
|
|
706
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
707
|
+
"button",
|
|
708
|
+
{
|
|
709
|
+
type: "button",
|
|
710
|
+
disabled: busy,
|
|
711
|
+
onClick: () => onPayWithPix(fee),
|
|
712
|
+
className: "mt-6 flex w-full items-center justify-center gap-2 rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
713
|
+
children: [
|
|
714
|
+
busy ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-4 w-4" }),
|
|
715
|
+
"Pagar ",
|
|
716
|
+
fmt.price.format(fee),
|
|
717
|
+
" com Pix"
|
|
718
|
+
]
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
] });
|
|
722
|
+
}
|
|
723
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
724
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-amber-300/60 bg-amber-50 p-4 dark:border-amber-400/30 dark:bg-amber-950/30", children: [
|
|
725
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2 text-sm font-semibold text-foreground", children: [
|
|
726
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-primary" }),
|
|
727
|
+
" Realize o pagamento para finalizar"
|
|
728
|
+
] }),
|
|
729
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Este c\xF3digo Pix \xE9 v\xE1lido por somente 5 minutos. N\xE3o feche esta p\xE1gina at\xE9 receber a confirma\xE7\xE3o do pagamento." })
|
|
730
|
+
] }),
|
|
731
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-6 font-heading text-lg font-bold text-foreground", children: "Escaneie ou copie este c\xF3digo para pagar" }),
|
|
732
|
+
/* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "mt-2 space-y-1 text-sm text-muted-foreground", children: [
|
|
733
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "1. Acesse o app do seu banco, pagamentos ou Internet Banking." }),
|
|
734
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "2. Escolha pagar via Pix." }),
|
|
735
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "3. Escaneie o QR Code ou use o Pix copia-e-cola:" })
|
|
736
|
+
] }),
|
|
737
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 w-fit rounded-xl border border-border bg-white p-3", children: /* @__PURE__ */ jsxRuntime.jsx(QrPlaceholder, { data: charge?.pixQrCode ?? PIX_PAYLOAD }) }),
|
|
738
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex max-w-md items-center gap-2", children: [
|
|
739
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "flex-1 truncate rounded-lg border border-border bg-muted px-3 py-2 text-xs text-muted-foreground", children: charge?.pixCopyPaste ?? PIX_PAYLOAD }),
|
|
740
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
741
|
+
"button",
|
|
742
|
+
{
|
|
743
|
+
type: "button",
|
|
744
|
+
onClick: onCopy,
|
|
745
|
+
className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-foreground px-3 py-2 text-xs font-medium text-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
746
|
+
children: [
|
|
747
|
+
copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "h-3.5 w-3.5" }),
|
|
748
|
+
" ",
|
|
749
|
+
copied ? "Copiado" : "Copiar"
|
|
750
|
+
]
|
|
751
|
+
}
|
|
752
|
+
)
|
|
753
|
+
] }),
|
|
218
754
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
219
|
-
"
|
|
755
|
+
"button",
|
|
220
756
|
{
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
children: state === "done" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) : n
|
|
757
|
+
type: "button",
|
|
758
|
+
onClick: onSimulatePaid,
|
|
759
|
+
className: "mt-6 w-full rounded-xl border border-primary py-3 font-semibold text-primary transition-colors hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
760
|
+
children: "J\xE1 paguei \u2014 confirmar (simula\xE7\xE3o)"
|
|
226
761
|
}
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
|
|
762
|
+
)
|
|
763
|
+
] });
|
|
764
|
+
}
|
|
765
|
+
function SummaryRail({
|
|
766
|
+
title,
|
|
767
|
+
railState,
|
|
768
|
+
service,
|
|
769
|
+
professionalName,
|
|
770
|
+
slotStart,
|
|
771
|
+
contactSummary,
|
|
772
|
+
payment,
|
|
773
|
+
paymentReached,
|
|
774
|
+
labels,
|
|
775
|
+
fmt,
|
|
776
|
+
components: C,
|
|
777
|
+
serviceMeta
|
|
778
|
+
}) {
|
|
779
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sticky top-24 rounded-2xl border border-border bg-card p-5 shadow-sm", children: [
|
|
780
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-5 font-heading text-sm font-bold uppercase tracking-wide text-muted-foreground", children: title }),
|
|
781
|
+
/* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "space-y-6", children: [
|
|
782
|
+
/* @__PURE__ */ jsxRuntime.jsx(C.StepRailItem, { n: 1, label: labels.railService, state: railState.service, children: service ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-border bg-background p-3", children: [
|
|
783
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: service.name }),
|
|
784
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-0.5 flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
785
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
786
|
+
serviceMeta ? `${serviceMeta} \xB7 ` : "",
|
|
787
|
+
service.durationMinutes,
|
|
788
|
+
" min"
|
|
789
|
+
] }),
|
|
790
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold tabular-nums text-primary", children: fmt.price.format(service.price) })
|
|
791
|
+
] })
|
|
792
|
+
] }) : null }),
|
|
793
|
+
/* @__PURE__ */ jsxRuntime.jsx(C.StepRailItem, { n: 2, label: labels.railProfessional, state: railState.professional, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
794
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground", children: professionalName.charAt(0) }),
|
|
795
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground", children: professionalName })
|
|
796
|
+
] }) }),
|
|
797
|
+
/* @__PURE__ */ jsxRuntime.jsx(C.StepRailItem, { n: 3, label: labels.railDatetime, state: railState.datetime, children: slotStart && service ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm tabular-nums text-foreground", children: formatSlotRange(slotStart, service.durationMinutes, fmt) }) : null }),
|
|
798
|
+
/* @__PURE__ */ jsxRuntime.jsx(C.StepRailItem, { n: 4, label: labels.railContact, state: railState.contact, children: contactSummary ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: contactSummary }) : null }),
|
|
799
|
+
payment ? /* @__PURE__ */ jsxRuntime.jsx(C.StepRailItem, { n: 5, label: labels.railPayment, state: railState.payment, children: paymentReached ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 text-sm text-foreground", children: [
|
|
800
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-3.5 w-3.5 text-primary" }),
|
|
801
|
+
" Pix \xE0 vista"
|
|
802
|
+
] }) : null }) : null
|
|
803
|
+
] })
|
|
230
804
|
] });
|
|
231
805
|
}
|
|
232
806
|
function BookingWidget() {
|
|
233
807
|
const {
|
|
234
808
|
services,
|
|
809
|
+
servicesLoading,
|
|
235
810
|
professionalName,
|
|
236
811
|
businessHours,
|
|
237
812
|
payment,
|
|
238
813
|
paymentProvider,
|
|
239
814
|
onIdentityVerified,
|
|
240
815
|
labels,
|
|
816
|
+
brand,
|
|
817
|
+
components: C,
|
|
241
818
|
window: bookingWindow,
|
|
242
819
|
locale,
|
|
243
820
|
currency
|
|
@@ -265,6 +842,12 @@ function BookingWidget() {
|
|
|
265
842
|
const [charge, setCharge] = react.useState(null);
|
|
266
843
|
const [charging, setCharging] = react.useState(false);
|
|
267
844
|
const pollRef = react.useRef(null);
|
|
845
|
+
react.useEffect(() => {
|
|
846
|
+
if (preselected && step === "overview") {
|
|
847
|
+
setServiceId(preselected);
|
|
848
|
+
setStep("datetime");
|
|
849
|
+
}
|
|
850
|
+
}, [preselected]);
|
|
268
851
|
react.useEffect(() => {
|
|
269
852
|
if (!(step === "payment" && paymentStep === "pix" && charge && paymentProvider)) return;
|
|
270
853
|
let active = true;
|
|
@@ -298,7 +881,9 @@ function BookingWidget() {
|
|
|
298
881
|
const bookableSlots = slots.filter((s) => new Date(s.start).getTime() >= minTime);
|
|
299
882
|
const service = services.find((s) => s.id === serviceId) ?? null;
|
|
300
883
|
const maxWeekOffset = Math.floor(bookingWindow.maxAdvanceDays / 7);
|
|
301
|
-
|
|
884
|
+
function contactForSubmit() {
|
|
885
|
+
return { ...contact, phone: `${country.dial} ${contact.phone}` };
|
|
886
|
+
}
|
|
302
887
|
function startBooking(id) {
|
|
303
888
|
setServiceId(id ?? serviceId ?? services[0]?.id ?? null);
|
|
304
889
|
setDate(todayIso);
|
|
@@ -308,7 +893,7 @@ function BookingWidget() {
|
|
|
308
893
|
async function handleConfirm() {
|
|
309
894
|
if (!serviceId || !slotStart || !contact.name.trim()) return;
|
|
310
895
|
try {
|
|
311
|
-
await submit({ serviceId, startsAt: slotStart, contact });
|
|
896
|
+
await submit({ serviceId, startsAt: slotStart, contact: contactForSubmit() });
|
|
312
897
|
setStep("success");
|
|
313
898
|
} catch {
|
|
314
899
|
}
|
|
@@ -343,17 +928,17 @@ function BookingWidget() {
|
|
|
343
928
|
setStep("payment");
|
|
344
929
|
} else void handleConfirm();
|
|
345
930
|
}
|
|
346
|
-
async function payWithPix(fee
|
|
347
|
-
if (charging) return;
|
|
931
|
+
async function payWithPix(fee) {
|
|
932
|
+
if (charging || !service || !slotStart) return;
|
|
348
933
|
setCharging(true);
|
|
349
934
|
try {
|
|
350
|
-
await submit({ serviceId:
|
|
935
|
+
await submit({ serviceId: service.id, startsAt: slotStart, contact: contactForSubmit() });
|
|
351
936
|
if (paymentProvider) {
|
|
352
937
|
const c = await paymentProvider.createCharge({
|
|
353
938
|
amount: fee,
|
|
354
939
|
currency,
|
|
355
940
|
method: "pix",
|
|
356
|
-
description:
|
|
941
|
+
description: service.name,
|
|
357
942
|
customer: { name: contact.name, email: contact.email || void 0, phone: contact.phone || void 0 }
|
|
358
943
|
});
|
|
359
944
|
setCharge(c);
|
|
@@ -364,61 +949,32 @@ function BookingWidget() {
|
|
|
364
949
|
setCharging(false);
|
|
365
950
|
}
|
|
366
951
|
}
|
|
952
|
+
function copyPixPayload() {
|
|
953
|
+
const payload = charge?.pixCopyPaste ?? PIX_PAYLOAD;
|
|
954
|
+
try {
|
|
955
|
+
void navigator.clipboard?.writeText(payload);
|
|
956
|
+
setCopied(true);
|
|
957
|
+
} catch {
|
|
958
|
+
setCopied(true);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
367
961
|
if (step === "overview") {
|
|
368
962
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-4xl", children: [
|
|
369
|
-
/* @__PURE__ */ jsxRuntime.jsx(BrandHeader, {}),
|
|
370
|
-
/* @__PURE__ */ jsxRuntime.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between py-2 text-sm", children: [
|
|
385
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: DAY_NAMES[day] }),
|
|
386
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: open ? "tabular-nums text-foreground" : "text-muted-foreground", children: open ? `${businessHours.start} \u2013 ${businessHours.end}` : "Fechado" })
|
|
387
|
-
] }, day);
|
|
388
|
-
}) })
|
|
389
|
-
] })
|
|
390
|
-
] }),
|
|
391
|
-
/* @__PURE__ */ jsxRuntime.jsx("aside", { className: "lg:sticky lg:top-24 lg:self-start", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl border border-border bg-card p-5 shadow-sm", children: [
|
|
392
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
393
|
-
"button",
|
|
394
|
-
{
|
|
395
|
-
type: "button",
|
|
396
|
-
onClick: () => startBooking(),
|
|
397
|
-
className: "w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
398
|
-
children: "Agendar agora"
|
|
399
|
-
}
|
|
400
|
-
),
|
|
401
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 space-y-2 border-t border-border pt-4 text-xs text-muted-foreground", children: [
|
|
402
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2", children: [
|
|
403
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { className: "h-3.5 w-3.5 text-primary" }),
|
|
404
|
-
" Consulta 100% online"
|
|
405
|
-
] }),
|
|
406
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2", children: [
|
|
407
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "h-3.5 w-3.5 text-primary" }),
|
|
408
|
-
" Atende todo o Brasil"
|
|
409
|
-
] }),
|
|
410
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2", children: [
|
|
411
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-3.5 w-3.5 text-primary" }),
|
|
412
|
-
" Aceitamos Pix"
|
|
413
|
-
] }),
|
|
414
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2", children: [
|
|
415
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-3.5 w-3.5 text-primary" }),
|
|
416
|
-
" ",
|
|
417
|
-
professionalName
|
|
418
|
-
] })
|
|
419
|
-
] })
|
|
420
|
-
] }) })
|
|
421
|
-
] })
|
|
963
|
+
/* @__PURE__ */ jsxRuntime.jsx(C.BrandHeader, { brand }),
|
|
964
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
965
|
+
OverviewStep,
|
|
966
|
+
{
|
|
967
|
+
services,
|
|
968
|
+
servicesLoading,
|
|
969
|
+
businessHours,
|
|
970
|
+
professionalName,
|
|
971
|
+
labels,
|
|
972
|
+
brand,
|
|
973
|
+
fmt,
|
|
974
|
+
components: C,
|
|
975
|
+
onStartBooking: startBooking
|
|
976
|
+
}
|
|
977
|
+
)
|
|
422
978
|
] });
|
|
423
979
|
}
|
|
424
980
|
const railState = {
|
|
@@ -428,9 +984,10 @@ function BookingWidget() {
|
|
|
428
984
|
contact: step === "payment" || step === "success" ? "done" : step === "contact" ? "active" : "todo",
|
|
429
985
|
payment: step === "success" ? "done" : step === "payment" ? "active" : "todo"
|
|
430
986
|
};
|
|
431
|
-
const railTitle = step === "payment" && paymentStep === "pix" ?
|
|
987
|
+
const railTitle = step === "payment" && paymentStep === "pix" ? labels.railWaitingPayment : labels.railTitle;
|
|
988
|
+
const contactSummary = (contact.name || contact.phone) && (step === "payment" || step === "success" || contactStep === "details") ? `${contact.name || "Contato"}${contact.phone ? ` \xB7 ${country.dial} ${contact.phone}` : ""}` : null;
|
|
432
989
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-4xl", children: [
|
|
433
|
-
/* @__PURE__ */ jsxRuntime.jsx(BrandHeader, {}),
|
|
990
|
+
/* @__PURE__ */ jsxRuntime.jsx(C.BrandHeader, { brand }),
|
|
434
991
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_18rem]", children: [
|
|
435
992
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl border border-border bg-card p-6 md:p-7 shadow-sm", children: [
|
|
436
993
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-5 flex items-center gap-1.5 lg:hidden", children: ["datetime", "contact"].map((s) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: `h-1.5 flex-1 rounded-full ${railState[s] !== "todo" ? "bg-primary" : "bg-muted"}` }, s)) }),
|
|
@@ -443,470 +1000,226 @@ function BookingWidget() {
|
|
|
443
1000
|
children: [
|
|
444
1001
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" }),
|
|
445
1002
|
" ",
|
|
446
|
-
step === "datetime" ?
|
|
1003
|
+
step === "datetime" ? labels.backToServices : labels.back
|
|
447
1004
|
]
|
|
448
1005
|
}
|
|
449
1006
|
) : null,
|
|
450
|
-
step === "datetime" ? /* @__PURE__ */ jsxRuntime.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
)
|
|
483
|
-
|
|
1007
|
+
step === "datetime" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1008
|
+
DateTimeStep,
|
|
1009
|
+
{
|
|
1010
|
+
labels,
|
|
1011
|
+
fmt,
|
|
1012
|
+
week,
|
|
1013
|
+
weekOffset,
|
|
1014
|
+
maxWeekOffset,
|
|
1015
|
+
onWeekOffset: setWeekOffset,
|
|
1016
|
+
businessHours,
|
|
1017
|
+
date,
|
|
1018
|
+
onSelectDate: (iso) => {
|
|
1019
|
+
setDate(iso);
|
|
1020
|
+
setSlotStart(null);
|
|
1021
|
+
},
|
|
1022
|
+
slots: bookableSlots,
|
|
1023
|
+
slotsLoading,
|
|
1024
|
+
slotStart,
|
|
1025
|
+
onSelectSlot: setSlotStart,
|
|
1026
|
+
onContinue: () => setStep("contact")
|
|
1027
|
+
}
|
|
1028
|
+
) : null,
|
|
1029
|
+
step === "contact" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1030
|
+
ContactStep,
|
|
1031
|
+
{
|
|
1032
|
+
labels,
|
|
1033
|
+
contact,
|
|
1034
|
+
onContactChange: setContact,
|
|
1035
|
+
contactStep,
|
|
1036
|
+
country,
|
|
1037
|
+
onCountryChange: (iso2) => {
|
|
1038
|
+
const c = core.getCountry(iso2);
|
|
1039
|
+
setCountryIso(c.iso2);
|
|
1040
|
+
setContact((prev) => ({ ...prev, phone: core.maskPhone(core.unmaskPhone(prev.phone), c.mask) }));
|
|
1041
|
+
},
|
|
1042
|
+
code,
|
|
1043
|
+
onCodeChange: (v) => {
|
|
1044
|
+
setCode(v);
|
|
1045
|
+
setCodeError(false);
|
|
1046
|
+
},
|
|
1047
|
+
codeError,
|
|
1048
|
+
onSendCode: sendCode,
|
|
1049
|
+
onConfirmCode: confirmCode,
|
|
1050
|
+
onBackToPhone: () => setContactStep("phone"),
|
|
1051
|
+
onSubmitDetails: submitDetails,
|
|
1052
|
+
payment,
|
|
1053
|
+
submitting,
|
|
1054
|
+
error
|
|
1055
|
+
}
|
|
1056
|
+
) : null,
|
|
1057
|
+
step === "payment" && service && payment ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1058
|
+
PaymentStep,
|
|
1059
|
+
{
|
|
1060
|
+
service,
|
|
1061
|
+
payment,
|
|
1062
|
+
paymentStep,
|
|
1063
|
+
charge,
|
|
1064
|
+
busy: submitting || charging,
|
|
1065
|
+
error,
|
|
1066
|
+
copied,
|
|
1067
|
+
fmt,
|
|
1068
|
+
onPayWithPix: (fee) => void payWithPix(fee),
|
|
1069
|
+
onCopy: copyPixPayload,
|
|
1070
|
+
onSimulatePaid: () => setStep("success")
|
|
1071
|
+
}
|
|
1072
|
+
) : null,
|
|
1073
|
+
step === "success" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1074
|
+
C.SuccessPanel,
|
|
1075
|
+
{
|
|
1076
|
+
service,
|
|
1077
|
+
slotStart,
|
|
1078
|
+
professionalName,
|
|
1079
|
+
labels,
|
|
1080
|
+
fmt
|
|
1081
|
+
}
|
|
1082
|
+
) : null
|
|
1083
|
+
] }),
|
|
1084
|
+
/* @__PURE__ */ jsxRuntime.jsx("aside", { className: "hidden lg:block", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1085
|
+
SummaryRail,
|
|
1086
|
+
{
|
|
1087
|
+
title: railTitle,
|
|
1088
|
+
railState,
|
|
1089
|
+
service,
|
|
1090
|
+
professionalName,
|
|
1091
|
+
slotStart,
|
|
1092
|
+
contactSummary,
|
|
1093
|
+
payment,
|
|
1094
|
+
paymentReached: step === "payment" || step === "success",
|
|
1095
|
+
labels,
|
|
1096
|
+
fmt,
|
|
1097
|
+
components: C,
|
|
1098
|
+
serviceMeta: brand.serviceMeta
|
|
1099
|
+
}
|
|
1100
|
+
) })
|
|
1101
|
+
] })
|
|
1102
|
+
] });
|
|
1103
|
+
}
|
|
1104
|
+
function DefaultBrandHeader({ brand }) {
|
|
1105
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-5 flex items-center gap-3 rounded-2xl border border-border bg-card px-5 py-4 shadow-sm", children: [
|
|
1106
|
+
brand.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: brand.logoUrl, alt: brand.name, className: "h-11 w-11 rounded-xl object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-11 w-11 items-center justify-center rounded-xl bg-primary text-lg font-bold text-primary-foreground", children: brand.initials }),
|
|
1107
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1108
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-heading text-base font-bold text-foreground", children: brand.name }),
|
|
1109
|
+
brand.tagline ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1110
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "h-3.5 w-3.5 text-primary" }),
|
|
1111
|
+
" ",
|
|
1112
|
+
brand.tagline
|
|
1113
|
+
] }) : null
|
|
1114
|
+
] })
|
|
1115
|
+
] });
|
|
1116
|
+
}
|
|
1117
|
+
function DefaultServiceCard({ service, featured, onSelect, fmt, brand }) {
|
|
1118
|
+
const Icon = brand.serviceIcon;
|
|
1119
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1120
|
+
"button",
|
|
1121
|
+
{
|
|
1122
|
+
type: "button",
|
|
1123
|
+
onClick: onSelect,
|
|
1124
|
+
className: "group flex w-full items-center gap-4 rounded-2xl border border-border p-4 text-left transition-colors hover:border-primary hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1125
|
+
children: [
|
|
1126
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-accent text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-5 w-5" }) }),
|
|
1127
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
1128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
1129
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: service.name }),
|
|
1130
|
+
featured && brand.featuredBadge ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-accent px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary", children: brand.featuredBadge }) : null
|
|
484
1131
|
] }),
|
|
485
|
-
/* @__PURE__ */ jsxRuntime.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
type: "button",
|
|
492
|
-
disabled: closed,
|
|
493
|
-
onClick: () => {
|
|
494
|
-
setDate(d.iso);
|
|
495
|
-
setSlotStart(null);
|
|
496
|
-
},
|
|
497
|
-
className: [
|
|
498
|
-
"flex flex-col items-center gap-0.5 rounded-xl border py-2.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
499
|
-
closed ? "cursor-not-allowed border-transparent text-muted-foreground/40" : selected ? "border-primary bg-primary text-primary-foreground" : "border-border text-foreground hover:border-primary/60 hover:bg-accent/40"
|
|
500
|
-
].join(" "),
|
|
501
|
-
children: [
|
|
502
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium capitalize opacity-80", children: fmt.weekday.format(d.date).replace(".", "") }),
|
|
503
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold tabular-nums", children: fmt.dayNum.format(d.date) })
|
|
504
|
-
]
|
|
505
|
-
},
|
|
506
|
-
d.iso
|
|
507
|
-
);
|
|
508
|
-
}) }),
|
|
509
|
-
date ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6", children: [
|
|
510
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-sm font-semibold text-foreground", children: "Hor\xE1rios dispon\xEDveis" }),
|
|
511
|
-
slotsLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-4", children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 animate-pulse rounded-lg bg-muted" }, i)) }) : bookableSlots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-dashed border-border p-6 text-center text-sm text-muted-foreground", children: "Nenhum hor\xE1rio dispon\xEDvel neste dia. Tente outra data." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-4", children: bookableSlots.map((s) => {
|
|
512
|
-
const selected = slotStart === s.start;
|
|
513
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
514
|
-
"button",
|
|
515
|
-
{
|
|
516
|
-
type: "button",
|
|
517
|
-
onClick: () => setSlotStart(s.start),
|
|
518
|
-
className: [
|
|
519
|
-
"rounded-lg border py-2.5 text-sm font-semibold tabular-nums transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
520
|
-
selected ? "border-primary bg-primary text-primary-foreground" : "border-border text-foreground hover:border-primary/60 hover:bg-accent/40"
|
|
521
|
-
].join(" "),
|
|
522
|
-
children: fmt.time.format(new Date(s.start))
|
|
523
|
-
},
|
|
524
|
-
s.start
|
|
525
|
-
);
|
|
526
|
-
}) })
|
|
527
|
-
] }) : null,
|
|
528
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
529
|
-
"button",
|
|
530
|
-
{
|
|
531
|
-
type: "button",
|
|
532
|
-
disabled: !slotStart,
|
|
533
|
-
onClick: () => setStep("contact"),
|
|
534
|
-
className: "mt-6 w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
535
|
-
children: "Continuar"
|
|
536
|
-
}
|
|
537
|
-
)
|
|
538
|
-
] }) : null,
|
|
539
|
-
step === "contact" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
540
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-xl font-bold text-foreground", children: labels.contactStep }),
|
|
541
|
-
contactStep === "phone" ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => {
|
|
542
|
-
e.preventDefault();
|
|
543
|
-
sendCode();
|
|
544
|
-
}, children: [
|
|
545
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Preencha as informa\xE7\xF5es abaixo para que seu agendamento seja identificado." }),
|
|
546
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5", children: [
|
|
547
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-phone", className: "mb-1 block text-sm font-medium text-foreground", children: "Celular" }),
|
|
548
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
|
|
549
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
550
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
551
|
-
"select",
|
|
552
|
-
{
|
|
553
|
-
"aria-label": "Pa\xEDs",
|
|
554
|
-
value: countryIso,
|
|
555
|
-
onChange: (e) => {
|
|
556
|
-
const c = core.getCountry(e.target.value);
|
|
557
|
-
setCountryIso(c.iso2);
|
|
558
|
-
setContact({ ...contact, phone: core.maskPhone(core.unmaskPhone(contact.phone), c.mask) });
|
|
559
|
-
},
|
|
560
|
-
className: "h-full appearance-none rounded-l-xl border border-r-0 border-border bg-muted py-3 pl-3 pr-8 text-sm font-medium text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
561
|
-
children: core.COUNTRIES.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.iso2, children: [
|
|
562
|
-
c.flag,
|
|
563
|
-
" ",
|
|
564
|
-
c.dial
|
|
565
|
-
] }, c.iso2))
|
|
566
|
-
}
|
|
567
|
-
),
|
|
568
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" })
|
|
569
|
-
] }),
|
|
570
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
571
|
-
"input",
|
|
572
|
-
{
|
|
573
|
-
id: "bk-phone",
|
|
574
|
-
type: "tel",
|
|
575
|
-
autoComplete: "tel",
|
|
576
|
-
inputMode: "tel",
|
|
577
|
-
value: contact.phone,
|
|
578
|
-
onChange: (e) => setContact({ ...contact, phone: core.maskPhone(e.target.value, country.mask) }),
|
|
579
|
-
className: "w-full rounded-r-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
580
|
-
placeholder: `Ex.: ${core.maskPhone("11987654321", country.mask)}`
|
|
581
|
-
}
|
|
582
|
-
)
|
|
583
|
-
] }),
|
|
584
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Enviaremos um c\xF3digo para confirmar que o n\xFAmero de telefone \xE9 seu. Usaremos este n\xFAmero para associar as suas informa\xE7\xF5es \xE0 sua conta." })
|
|
585
|
-
] }),
|
|
586
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
587
|
-
"button",
|
|
588
|
-
{
|
|
589
|
-
type: "submit",
|
|
590
|
-
disabled: core.unmaskPhone(contact.phone).length < 8,
|
|
591
|
-
className: "mt-5 w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
592
|
-
children: "Enviar c\xF3digo via WhatsApp"
|
|
593
|
-
}
|
|
594
|
-
)
|
|
595
|
-
] }) : null,
|
|
596
|
-
contactStep === "details" ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => {
|
|
597
|
-
e.preventDefault();
|
|
598
|
-
submitDetails();
|
|
599
|
-
}, children: [
|
|
600
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Preencha as informa\xE7\xF5es abaixo para que seu agendamento seja identificado." }),
|
|
601
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex items-center justify-between rounded-xl border border-border bg-muted/40 px-4 py-3", children: [
|
|
602
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 text-sm text-foreground", children: [
|
|
603
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "h-4 w-4 text-primary" }),
|
|
604
|
-
" Celular verificado \xB7 ",
|
|
605
|
-
country.flag,
|
|
606
|
-
" ",
|
|
607
|
-
country.dial,
|
|
608
|
-
" ",
|
|
609
|
-
contact.phone
|
|
610
|
-
] }),
|
|
611
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
612
|
-
"button",
|
|
613
|
-
{
|
|
614
|
-
type: "button",
|
|
615
|
-
onClick: () => setContactStep("phone"),
|
|
616
|
-
className: "rounded text-sm font-medium text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
617
|
-
children: "Trocar telefone"
|
|
618
|
-
}
|
|
619
|
-
)
|
|
620
|
-
] }),
|
|
621
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 space-y-3", children: [
|
|
622
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
623
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-name", className: "mb-1 block text-sm font-medium text-foreground", children: "Nome completo *" }),
|
|
624
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
625
|
-
"input",
|
|
626
|
-
{
|
|
627
|
-
id: "bk-name",
|
|
628
|
-
type: "text",
|
|
629
|
-
autoComplete: "name",
|
|
630
|
-
value: contact.name,
|
|
631
|
-
onChange: (e) => setContact({ ...contact, name: e.target.value }),
|
|
632
|
-
className: "w-full rounded-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
633
|
-
placeholder: "Seu nome"
|
|
634
|
-
}
|
|
635
|
-
)
|
|
636
|
-
] }),
|
|
637
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
638
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-email", className: "mb-1 block text-sm font-medium text-foreground", children: "E-mail" }),
|
|
639
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
640
|
-
"input",
|
|
641
|
-
{
|
|
642
|
-
id: "bk-email",
|
|
643
|
-
type: "email",
|
|
644
|
-
autoComplete: "email",
|
|
645
|
-
inputMode: "email",
|
|
646
|
-
value: contact.email,
|
|
647
|
-
onChange: (e) => setContact({ ...contact, email: e.target.value }),
|
|
648
|
-
className: "w-full rounded-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
649
|
-
placeholder: "voce@email.com"
|
|
650
|
-
}
|
|
651
|
-
),
|
|
652
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Enviaremos uma confirma\xE7\xE3o e um lembrete antes do agendamento." })
|
|
653
|
-
] }),
|
|
654
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
655
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "bk-notes", className: "mb-1 block text-sm font-medium text-foreground", children: "Observa\xE7\xE3o" }),
|
|
656
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
657
|
-
"textarea",
|
|
658
|
-
{
|
|
659
|
-
id: "bk-notes",
|
|
660
|
-
rows: 3,
|
|
661
|
-
value: contact.notes,
|
|
662
|
-
onChange: (e) => setContact({ ...contact, notes: e.target.value }),
|
|
663
|
-
className: "w-full resize-none rounded-xl border border-border bg-background px-4 py-3 text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring",
|
|
664
|
-
placeholder: "Use esse espa\xE7o para deixar uma observa\xE7\xE3o sobre este agendamento"
|
|
665
|
-
}
|
|
666
|
-
)
|
|
667
|
-
] })
|
|
668
|
-
] }),
|
|
669
|
-
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-3 text-sm text-destructive", children: "N\xE3o foi poss\xEDvel concluir o agendamento. Tente novamente." }) : null,
|
|
670
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
671
|
-
"button",
|
|
672
|
-
{
|
|
673
|
-
type: "submit",
|
|
674
|
-
disabled: !contact.name.trim() || submitting,
|
|
675
|
-
className: "mt-5 flex w-full items-center justify-center gap-2 rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
676
|
-
children: [
|
|
677
|
-
submitting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }),
|
|
678
|
-
payment ? "Salvar e continuar" : labels.confirmCta
|
|
679
|
-
]
|
|
680
|
-
}
|
|
681
|
-
),
|
|
682
|
-
!payment ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-3 flex items-center justify-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
683
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-3.5 w-3.5" }),
|
|
684
|
-
" Pagamento via Pix \xB7 Sem cobran\xE7a agora"
|
|
685
|
-
] }) : null
|
|
686
|
-
] }) : null
|
|
687
|
-
] }) : null,
|
|
688
|
-
step === "contact" && contactStep === "code" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-foreground/40 p-4", role: "dialog", "aria-modal": "true", "aria-label": "Verifica\xE7\xE3o de c\xF3digo", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-6 shadow-xl", children: [
|
|
689
|
-
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-heading text-lg font-bold text-foreground", children: "Insira o c\xF3digo" }),
|
|
690
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Enviamos um c\xF3digo via WhatsApp para voc\xEA confirmar seu n\xFAmero." }),
|
|
691
|
-
/* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => {
|
|
692
|
-
e.preventDefault();
|
|
693
|
-
confirmCode();
|
|
694
|
-
}, children: [
|
|
695
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
696
|
-
"input",
|
|
697
|
-
{
|
|
698
|
-
type: "text",
|
|
699
|
-
inputMode: "numeric",
|
|
700
|
-
value: code,
|
|
701
|
-
autoFocus: true,
|
|
702
|
-
onChange: (e) => {
|
|
703
|
-
setCode(e.target.value);
|
|
704
|
-
setCodeError(false);
|
|
705
|
-
},
|
|
706
|
-
className: "mt-4 w-full rounded-xl border border-border bg-background px-4 py-3 text-center text-lg tracking-[0.4em] text-foreground outline-none transition-colors focus:border-primary focus-visible:ring-2 focus-visible:ring-ring placeholder:text-sm placeholder:tracking-normal",
|
|
707
|
-
placeholder: "Digite o c\xF3digo"
|
|
708
|
-
}
|
|
709
|
-
),
|
|
710
|
-
codeError ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-2 text-sm text-destructive", children: "C\xF3digo inv\xE1lido. Para testar, use 0000." }) : null,
|
|
711
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
712
|
-
"button",
|
|
713
|
-
{
|
|
714
|
-
type: "submit",
|
|
715
|
-
className: "mt-4 w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
716
|
-
children: "Confirmar"
|
|
717
|
-
}
|
|
718
|
-
)
|
|
1132
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mt-0.5 flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
1133
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
1134
|
+
" ",
|
|
1135
|
+
service.durationMinutes,
|
|
1136
|
+
" min",
|
|
1137
|
+
brand.serviceMeta ? ` \xB7 ${brand.serviceMeta}` : ""
|
|
719
1138
|
] }),
|
|
720
|
-
/* @__PURE__ */ jsxRuntime.
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
step === "payment" && service ? (() => {
|
|
726
|
-
const feePct = payment?.reservationFeePercent ?? 100;
|
|
727
|
-
const fee = Math.round(service.price * feePct) / 100;
|
|
728
|
-
const remaining = Math.round((service.price - fee) * 100) / 100;
|
|
729
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { children: paymentStep === "choose" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
730
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl border border-border p-4", children: [
|
|
731
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between font-semibold text-foreground", children: [
|
|
732
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: service.name }),
|
|
733
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: fmt.price.format(service.price) })
|
|
734
|
-
] }),
|
|
735
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-2 text-sm", children: [
|
|
736
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-foreground", children: [
|
|
737
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
738
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-4 w-4 items-center justify-center rounded bg-primary text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3" }) }),
|
|
739
|
-
"Taxa de reserva ",
|
|
740
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground", children: [
|
|
741
|
-
feePct,
|
|
742
|
-
"%"
|
|
743
|
-
] })
|
|
744
|
-
] }),
|
|
745
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: fmt.price.format(fee) })
|
|
746
|
-
] }),
|
|
747
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-muted-foreground", children: [
|
|
748
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
749
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-4 w-4 rounded border border-border" }),
|
|
750
|
-
" Valor restante"
|
|
751
|
-
] }),
|
|
752
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: remaining <= 0 ? "Gr\xE1tis" : fmt.price.format(remaining) })
|
|
753
|
-
] }),
|
|
754
|
-
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "flex items-center gap-1 text-sm font-medium text-primary hover:underline", children: [
|
|
755
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
|
|
756
|
-
" Adicionar cupom"
|
|
757
|
-
] })
|
|
758
|
-
] }),
|
|
759
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-between border-t border-dashed border-border pt-3 font-semibold text-foreground", children: [
|
|
760
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Valor total" }),
|
|
761
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: fmt.price.format(service.price) })
|
|
762
|
-
] })
|
|
763
|
-
] }),
|
|
764
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex items-center justify-between", children: [
|
|
765
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-lg font-bold text-foreground", children: "Escolha como pagar" }),
|
|
766
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
767
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "h-3.5 w-3.5 text-primary" }),
|
|
768
|
-
" Pagamento seguro"
|
|
769
|
-
] })
|
|
770
|
-
] }),
|
|
771
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 rounded-2xl border-2 border-primary bg-accent/20 p-4", children: [
|
|
772
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2 font-semibold text-foreground", children: [
|
|
773
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }) }),
|
|
774
|
-
"Pix"
|
|
775
|
-
] }),
|
|
776
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3", children: [
|
|
777
|
-
{ icon: lucideReact.Check, t: 'Finalize o agendamento clicando em "Pagar com Pix".' },
|
|
778
|
-
{ icon: lucideReact.QrCode, t: "Mostraremos um QR Code e o Pix copia-e-cola." },
|
|
779
|
-
{ icon: lucideReact.CalendarCheck, t: "Ap\xF3s o pagamento, seu agendamento \xE9 reservado." }
|
|
780
|
-
].map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-muted-foreground", children: [
|
|
781
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-2 flex h-8 w-8 items-center justify-center rounded-lg bg-accent text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(s.icon, { className: "h-4 w-4" }) }),
|
|
782
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-foreground", children: [
|
|
783
|
-
i + 1,
|
|
784
|
-
"."
|
|
785
|
-
] }),
|
|
786
|
-
" ",
|
|
787
|
-
s.t
|
|
788
|
-
] }, i)) })
|
|
789
|
-
] }),
|
|
790
|
-
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-3 text-sm text-destructive", children: "N\xE3o foi poss\xEDvel iniciar o pagamento. Tente novamente." }) : null,
|
|
791
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
792
|
-
"button",
|
|
793
|
-
{
|
|
794
|
-
type: "button",
|
|
795
|
-
disabled: submitting || charging,
|
|
796
|
-
onClick: () => void payWithPix(fee, service.id, service.name),
|
|
797
|
-
className: "mt-6 flex w-full items-center justify-center gap-2 rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
798
|
-
children: [
|
|
799
|
-
submitting || charging ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-4 w-4" }),
|
|
800
|
-
"Pagar ",
|
|
801
|
-
fmt.price.format(fee),
|
|
802
|
-
" com Pix"
|
|
803
|
-
]
|
|
804
|
-
}
|
|
805
|
-
)
|
|
806
|
-
] }) : (
|
|
807
|
-
/* Pix QR — aguardando pagamento */
|
|
808
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
809
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-amber-300/60 bg-amber-50 p-4 dark:border-amber-400/30 dark:bg-amber-950/30", children: [
|
|
810
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2 text-sm font-semibold text-foreground", children: [
|
|
811
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-primary" }),
|
|
812
|
-
" Realize o pagamento para finalizar"
|
|
813
|
-
] }),
|
|
814
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Este c\xF3digo Pix \xE9 v\xE1lido por somente 5 minutos. N\xE3o feche esta p\xE1gina at\xE9 receber a confirma\xE7\xE3o do pagamento." })
|
|
815
|
-
] }),
|
|
816
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-6 font-heading text-lg font-bold text-foreground", children: "Escaneie ou copie este c\xF3digo para pagar" }),
|
|
817
|
-
/* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "mt-2 space-y-1 text-sm text-muted-foreground", children: [
|
|
818
|
-
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "1. Acesse o app do seu banco, pagamentos ou Internet Banking." }),
|
|
819
|
-
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "2. Escolha pagar via Pix." }),
|
|
820
|
-
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "3. Escaneie o QR Code ou use o Pix copia-e-cola:" })
|
|
821
|
-
] }),
|
|
822
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 w-fit rounded-xl border border-border bg-white p-3", children: /* @__PURE__ */ jsxRuntime.jsx(QrPlaceholder, { data: charge?.pixQrCode ?? PIX_PAYLOAD }) }),
|
|
823
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex max-w-md items-center gap-2", children: [
|
|
824
|
-
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "flex-1 truncate rounded-lg border border-border bg-muted px-3 py-2 text-xs text-muted-foreground", children: charge?.pixCopyPaste ?? PIX_PAYLOAD }),
|
|
825
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
826
|
-
"button",
|
|
827
|
-
{
|
|
828
|
-
type: "button",
|
|
829
|
-
onClick: () => {
|
|
830
|
-
const payload = charge?.pixCopyPaste ?? PIX_PAYLOAD;
|
|
831
|
-
try {
|
|
832
|
-
void navigator.clipboard?.writeText(payload);
|
|
833
|
-
setCopied(true);
|
|
834
|
-
} catch {
|
|
835
|
-
setCopied(true);
|
|
836
|
-
}
|
|
837
|
-
},
|
|
838
|
-
className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-foreground px-3 py-2 text-xs font-medium text-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
839
|
-
children: [
|
|
840
|
-
copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "h-3.5 w-3.5" }),
|
|
841
|
-
" ",
|
|
842
|
-
copied ? "Copiado" : "Copiar"
|
|
843
|
-
]
|
|
844
|
-
}
|
|
845
|
-
)
|
|
846
|
-
] }),
|
|
847
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
848
|
-
"button",
|
|
849
|
-
{
|
|
850
|
-
type: "button",
|
|
851
|
-
onClick: () => setStep("success"),
|
|
852
|
-
className: "mt-6 w-full rounded-xl border border-primary py-3 font-semibold text-primary transition-colors hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
853
|
-
children: "J\xE1 paguei \u2014 confirmar (simula\xE7\xE3o)"
|
|
854
|
-
}
|
|
855
|
-
)
|
|
856
|
-
] })
|
|
857
|
-
) });
|
|
858
|
-
})() : null,
|
|
859
|
-
step === "success" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-8 text-center", children: [
|
|
860
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-accent text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-8 w-8" }) }),
|
|
861
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-2xl font-bold text-foreground", children: labels.successTitle }),
|
|
862
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-2 max-w-sm text-muted-foreground", children: labels.successBody }),
|
|
863
|
-
service && slotStart ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto mt-6 max-w-sm rounded-2xl border border-border bg-muted/50 p-4 text-left", children: [
|
|
864
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-foreground", children: service.name }),
|
|
865
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 flex items-center gap-1.5 text-sm tabular-nums text-muted-foreground", children: [
|
|
866
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-4 w-4 text-primary" }),
|
|
867
|
-
" ",
|
|
868
|
-
formatSlotRange(slotStart, service.durationMinutes, fmt)
|
|
869
|
-
] }),
|
|
870
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 flex items-center gap-1.5 text-sm text-muted-foreground", children: [
|
|
871
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4 text-primary" }),
|
|
872
|
-
" ",
|
|
873
|
-
professionalName
|
|
874
|
-
] })
|
|
875
|
-
] }) : null
|
|
876
|
-
] }) : null
|
|
877
|
-
] }),
|
|
878
|
-
/* @__PURE__ */ jsxRuntime.jsx("aside", { className: "hidden lg:block", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sticky top-24 rounded-2xl border border-border bg-card p-5 shadow-sm", children: [
|
|
879
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-5 font-heading text-sm font-bold uppercase tracking-wide text-muted-foreground", children: railTitle }),
|
|
880
|
-
/* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "space-y-6", children: [
|
|
881
|
-
/* @__PURE__ */ jsxRuntime.jsx(StepRail, { n: 1, label: "Servi\xE7o", state: railState.service, children: service ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-border bg-background p-3", children: [
|
|
882
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: service.name }),
|
|
883
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-0.5 flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
884
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
885
|
-
"Online \xB7 ",
|
|
886
|
-
service.durationMinutes,
|
|
887
|
-
" min"
|
|
888
|
-
] }),
|
|
889
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold tabular-nums text-primary", children: fmt.price.format(service.price) })
|
|
890
|
-
] })
|
|
891
|
-
] }) : null }),
|
|
892
|
-
/* @__PURE__ */ jsxRuntime.jsx(StepRail, { n: 2, label: "Profissional", state: railState.professional, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
893
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground", children: professionalName.charAt(0) }),
|
|
894
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground", children: professionalName })
|
|
895
|
-
] }) }),
|
|
896
|
-
/* @__PURE__ */ jsxRuntime.jsx(StepRail, { n: 3, label: "Data e hor\xE1rio", state: railState.datetime, children: slotStart && service ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm tabular-nums text-foreground", children: formatSlotRange(slotStart, service.durationMinutes, fmt) }) : null }),
|
|
897
|
-
/* @__PURE__ */ jsxRuntime.jsx(StepRail, { n: 4, label: "Informa\xE7\xF5es pessoais", state: railState.contact, children: (contact.name || contact.phone) && (step === "payment" || step === "success" || contactStep === "details") ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-foreground", children: [
|
|
898
|
-
contact.name || "Contato",
|
|
899
|
-
contact.phone ? ` \xB7 ${country.dial} ${contact.phone}` : ""
|
|
900
|
-
] }) : null }),
|
|
901
|
-
payment ? /* @__PURE__ */ jsxRuntime.jsx(StepRail, { n: 5, label: "Pagamento", state: railState.payment, children: step === "payment" || step === "success" ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 text-sm text-foreground", children: [
|
|
902
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-3.5 w-3.5 text-primary" }),
|
|
903
|
-
" Pix \xE0 vista"
|
|
904
|
-
] }) : null }) : null
|
|
1139
|
+
service.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-sm text-muted-foreground", children: service.description }) : null
|
|
1140
|
+
] }),
|
|
1141
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-right", children: [
|
|
1142
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block font-semibold tabular-nums text-primary", children: fmt.price.format(service.price) }),
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "ml-auto mt-1 h-4 w-4 text-muted-foreground transition-transform group-hover:translate-x-0.5" })
|
|
905
1144
|
] })
|
|
906
|
-
]
|
|
1145
|
+
]
|
|
1146
|
+
}
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
function DefaultSidebarExtras({ professionalName, brand, onStartBooking, labels }) {
|
|
1150
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl border border-border bg-card p-5 shadow-sm", children: [
|
|
1151
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1152
|
+
"button",
|
|
1153
|
+
{
|
|
1154
|
+
type: "button",
|
|
1155
|
+
onClick: onStartBooking,
|
|
1156
|
+
className: "w-full rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1157
|
+
children: labels.bookNowCta
|
|
1158
|
+
}
|
|
1159
|
+
),
|
|
1160
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 space-y-2 border-t border-border pt-4 text-xs text-muted-foreground", children: [
|
|
1161
|
+
brand.highlights.map((h, i) => {
|
|
1162
|
+
const Icon = h.icon ?? lucideReact.User;
|
|
1163
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2", children: [
|
|
1164
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5 text-primary" }),
|
|
1165
|
+
" ",
|
|
1166
|
+
h.text
|
|
1167
|
+
] }, i);
|
|
1168
|
+
}),
|
|
1169
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2", children: [
|
|
1170
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-3.5 w-3.5 text-primary" }),
|
|
1171
|
+
" ",
|
|
1172
|
+
professionalName
|
|
1173
|
+
] })
|
|
907
1174
|
] })
|
|
908
1175
|
] });
|
|
909
1176
|
}
|
|
1177
|
+
function DefaultStepRailItem({ n, label, state, children }) {
|
|
1178
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "relative pl-11", children: [
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "absolute left-[15px] top-8 bottom-[-18px] w-px bg-border last:hidden" }),
|
|
1180
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1181
|
+
"span",
|
|
1182
|
+
{
|
|
1183
|
+
className: [
|
|
1184
|
+
"absolute left-0 top-0 flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition-colors",
|
|
1185
|
+
state === "done" ? "bg-primary text-primary-foreground" : state === "active" ? "bg-accent text-primary ring-2 ring-primary" : "bg-muted text-muted-foreground"
|
|
1186
|
+
].join(" "),
|
|
1187
|
+
children: state === "done" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) : n
|
|
1188
|
+
}
|
|
1189
|
+
),
|
|
1190
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `pt-1 text-sm font-semibold ${state === "todo" ? "text-muted-foreground" : "text-foreground"}`, children: label }),
|
|
1191
|
+
children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children }) : null
|
|
1192
|
+
] });
|
|
1193
|
+
}
|
|
1194
|
+
function DefaultSuccessPanel({ service, slotStart, professionalName, labels, fmt }) {
|
|
1195
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-8 text-center", children: [
|
|
1196
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-accent text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-8 w-8" }) }),
|
|
1197
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-2xl font-bold text-foreground", children: labels.successTitle }),
|
|
1198
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-2 max-w-sm text-muted-foreground", children: labels.successBody }),
|
|
1199
|
+
service && slotStart ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto mt-6 max-w-sm rounded-2xl border border-border bg-muted/50 p-4 text-left", children: [
|
|
1200
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-foreground", children: service.name }),
|
|
1201
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 flex items-center gap-1.5 text-sm tabular-nums text-muted-foreground", children: [
|
|
1202
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-4 w-4 text-primary" }),
|
|
1203
|
+
" ",
|
|
1204
|
+
formatSlotRange(slotStart, service.durationMinutes, fmt)
|
|
1205
|
+
] }),
|
|
1206
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 flex items-center gap-1.5 text-sm text-muted-foreground", children: [
|
|
1207
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4 text-primary" }),
|
|
1208
|
+
" ",
|
|
1209
|
+
professionalName
|
|
1210
|
+
] })
|
|
1211
|
+
] }) : null
|
|
1212
|
+
] });
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// src/public/components/defaults.ts
|
|
1216
|
+
var DEFAULT_BOOKING_COMPONENTS = {
|
|
1217
|
+
BrandHeader: DefaultBrandHeader,
|
|
1218
|
+
ServiceCard: DefaultServiceCard,
|
|
1219
|
+
SidebarExtras: DefaultSidebarExtras,
|
|
1220
|
+
StepRailItem: DefaultStepRailItem,
|
|
1221
|
+
SuccessPanel: DefaultSuccessPanel
|
|
1222
|
+
};
|
|
910
1223
|
|
|
911
1224
|
// src/public/createPublicBookingPlugin.ts
|
|
912
1225
|
var DEFAULT_LABELS = {
|
|
@@ -916,10 +1229,71 @@ var DEFAULT_LABELS = {
|
|
|
916
1229
|
contactStep: "Seus dados",
|
|
917
1230
|
confirmCta: "Confirmar agendamento",
|
|
918
1231
|
successTitle: "Agendamento confirmado!",
|
|
919
|
-
successBody: "Voc\xEA receber\xE1 os detalhes em breve. At\xE9 logo!"
|
|
1232
|
+
successBody: "Voc\xEA receber\xE1 os detalhes em breve. At\xE9 logo!",
|
|
1233
|
+
// overview
|
|
1234
|
+
servicesHeading: "Servi\xE7os",
|
|
1235
|
+
aboutHeading: "Sobre",
|
|
1236
|
+
openingHoursHeading: "Hor\xE1rio de funcionamento",
|
|
1237
|
+
closedLabel: "Fechado",
|
|
1238
|
+
bookNowCta: "Agendar agora",
|
|
1239
|
+
// datetime
|
|
1240
|
+
timezoneNote: "Fuso: Hor\xE1rio de Bras\xEDlia",
|
|
1241
|
+
slotsHeading: "Hor\xE1rios dispon\xEDveis",
|
|
1242
|
+
noSlots: "Nenhum hor\xE1rio dispon\xEDvel neste dia. Tente outra data.",
|
|
1243
|
+
continueCta: "Continuar",
|
|
1244
|
+
backToServices: "Servi\xE7os",
|
|
1245
|
+
back: "Voltar",
|
|
1246
|
+
prevWeek: "Semana anterior",
|
|
1247
|
+
nextWeek: "Pr\xF3xima semana",
|
|
1248
|
+
// contact
|
|
1249
|
+
contactIntro: "Preencha as informa\xE7\xF5es abaixo para que seu agendamento seja identificado.",
|
|
1250
|
+
phoneLabel: "Celular",
|
|
1251
|
+
phoneHint: "Enviaremos um c\xF3digo para confirmar que o n\xFAmero de telefone \xE9 seu. Usaremos este n\xFAmero para associar as suas informa\xE7\xF5es \xE0 sua conta.",
|
|
1252
|
+
sendCodeCta: "Enviar c\xF3digo via WhatsApp",
|
|
1253
|
+
codeTitle: "Insira o c\xF3digo",
|
|
1254
|
+
codeBody: "Enviamos um c\xF3digo via WhatsApp para voc\xEA confirmar seu n\xFAmero.",
|
|
1255
|
+
codeInvalid: "C\xF3digo inv\xE1lido. Para testar, use 0000.",
|
|
1256
|
+
codeConfirmCta: "Confirmar",
|
|
1257
|
+
codeResendHint: "Reenviar c\xF3digo via WhatsApp em 28 segundos.",
|
|
1258
|
+
phoneVerified: "Celular verificado",
|
|
1259
|
+
changePhone: "Trocar telefone",
|
|
1260
|
+
nameLabel: "Nome completo *",
|
|
1261
|
+
namePlaceholder: "Seu nome",
|
|
1262
|
+
emailLabel: "E-mail",
|
|
1263
|
+
emailPlaceholder: "voce@email.com",
|
|
1264
|
+
emailHint: "Enviaremos uma confirma\xE7\xE3o e um lembrete antes do agendamento.",
|
|
1265
|
+
notesLabel: "Observa\xE7\xE3o",
|
|
1266
|
+
notesPlaceholder: "Use esse espa\xE7o para deixar uma observa\xE7\xE3o sobre este agendamento",
|
|
1267
|
+
saveAndContinueCta: "Salvar e continuar",
|
|
1268
|
+
submitError: "N\xE3o foi poss\xEDvel concluir o agendamento. Tente novamente.",
|
|
1269
|
+
noChargeNote: "Pagamento via Pix \xB7 Sem cobran\xE7a agora",
|
|
1270
|
+
// right rail
|
|
1271
|
+
railTitle: "Passos para agendar",
|
|
1272
|
+
railWaitingPayment: "Aguardando pagamento",
|
|
1273
|
+
railService: "Servi\xE7o",
|
|
1274
|
+
railProfessional: "Profissional",
|
|
1275
|
+
railDatetime: "Data e hor\xE1rio",
|
|
1276
|
+
railContact: "Informa\xE7\xF5es pessoais",
|
|
1277
|
+
railPayment: "Pagamento"
|
|
920
1278
|
};
|
|
921
1279
|
var DEFAULT_WINDOW = { minAdvanceHours: 2, maxAdvanceDays: 21, slotInterval: 30 };
|
|
922
1280
|
var DEFAULT_HOURS = { daysOfWeek: [1, 2, 3, 4, 5, 6], start: "09:00", end: "18:00" };
|
|
1281
|
+
function resolveBrand(brand, labels) {
|
|
1282
|
+
const name = brand?.name ?? labels.title;
|
|
1283
|
+
return {
|
|
1284
|
+
name,
|
|
1285
|
+
initials: brand?.initials ?? name.charAt(0).toUpperCase(),
|
|
1286
|
+
logoUrl: brand?.logoUrl,
|
|
1287
|
+
tagline: brand?.tagline === void 0 ? "Agendamento online seguro" : brand.tagline,
|
|
1288
|
+
serviceIcon: brand?.serviceIcon ?? lucideReact.Video,
|
|
1289
|
+
serviceMeta: brand?.serviceMeta === void 0 ? "Online" : brand.serviceMeta,
|
|
1290
|
+
featuredBadge: brand?.featuredBadge === void 0 ? "Mais procurado" : brand.featuredBadge,
|
|
1291
|
+
highlights: brand?.highlights ?? [
|
|
1292
|
+
{ icon: lucideReact.CalendarCheck, text: "Confirma\xE7\xE3o imediata" },
|
|
1293
|
+
{ icon: lucideReact.ShieldCheck, text: "Agendamento seguro" }
|
|
1294
|
+
]
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
923
1297
|
var scheduleId = 0;
|
|
924
1298
|
function buildSchedules(professionalId, hours) {
|
|
925
1299
|
return hours.daysOfWeek.map((day) => ({
|
|
@@ -938,19 +1312,23 @@ function buildSchedules(professionalId, hours) {
|
|
|
938
1312
|
}
|
|
939
1313
|
function createPublicBookingPlugin(options) {
|
|
940
1314
|
const basePath = options.basePath ?? "/agendar";
|
|
941
|
-
const
|
|
1315
|
+
const mockProfessionalId = options.professional?.id ?? "pro-1";
|
|
1316
|
+
const professionalId = options.professional?.id ?? null;
|
|
942
1317
|
const professionalName = options.professional?.name ?? "Especialista";
|
|
943
1318
|
const window2 = { ...DEFAULT_WINDOW, ...options.window };
|
|
944
1319
|
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
1320
|
+
const brand = resolveBrand(options.brand, labels);
|
|
1321
|
+
const components = { ...DEFAULT_BOOKING_COMPONENTS, ...options.components };
|
|
945
1322
|
const currency = options.currency ?? "BRL";
|
|
946
1323
|
const locale = options.locale ?? "pt-BR";
|
|
947
1324
|
const hours = options.workingHours ?? DEFAULT_HOURS;
|
|
1325
|
+
const staticServices = options.services ?? null;
|
|
948
1326
|
const payment = options.payment?.enabled ? {
|
|
949
1327
|
reservationFeePercent: options.payment.reservationFeePercent ?? 100,
|
|
950
1328
|
method: options.payment.method ?? "pix"
|
|
951
1329
|
} : null;
|
|
952
1330
|
const professional = {
|
|
953
|
-
id:
|
|
1331
|
+
id: mockProfessionalId,
|
|
954
1332
|
name: professionalName,
|
|
955
1333
|
avatarUrl: null,
|
|
956
1334
|
locationId: null,
|
|
@@ -959,28 +1337,40 @@ function createPublicBookingPlugin(options) {
|
|
|
959
1337
|
};
|
|
960
1338
|
const mockSeed = options.seed ?? {
|
|
961
1339
|
professionals: [professional],
|
|
962
|
-
schedules: buildSchedules(
|
|
1340
|
+
schedules: buildSchedules(mockProfessionalId, hours),
|
|
963
1341
|
bookings: []
|
|
964
1342
|
};
|
|
965
1343
|
const provider = options.dataProvider ?? core.createSafeDataProvider(
|
|
966
|
-
() =>
|
|
967
|
-
|
|
1344
|
+
() => {
|
|
1345
|
+
if (!options.tenantId) {
|
|
1346
|
+
throw new Error(
|
|
1347
|
+
"[plugin-agenda/public] `tenantId` is required when a Supabase client is configured (setGlobalSupabaseClient)."
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
return createSupabasePublicBookingProvider({ tenantId: options.tenantId });
|
|
1351
|
+
},
|
|
1352
|
+
() => createMockPublicBookingProvider({
|
|
1353
|
+
seed: mockSeed,
|
|
1354
|
+
services: staticServices ?? [],
|
|
1355
|
+
professionalId: mockProfessionalId
|
|
1356
|
+
})
|
|
968
1357
|
);
|
|
969
|
-
const
|
|
1358
|
+
const base = {
|
|
970
1359
|
provider,
|
|
971
1360
|
professionalId,
|
|
972
1361
|
professionalName,
|
|
973
|
-
services: options.services,
|
|
974
1362
|
businessHours: hours,
|
|
975
1363
|
payment,
|
|
976
1364
|
paymentProvider: options.paymentProvider ?? null,
|
|
977
1365
|
onIdentityVerified: options.onIdentityVerified ?? null,
|
|
978
1366
|
window: window2,
|
|
979
1367
|
labels,
|
|
1368
|
+
brand,
|
|
1369
|
+
components,
|
|
980
1370
|
currency,
|
|
981
1371
|
locale
|
|
982
1372
|
};
|
|
983
|
-
const Provider = ({ children }) => react.createElement(BookingProvider, {
|
|
1373
|
+
const Provider = ({ children }) => react.createElement(BookingProvider, { base, staticServices, children });
|
|
984
1374
|
Provider.displayName = "PublicBookingProvider";
|
|
985
1375
|
const BookingScreen = () => react.createElement(
|
|
986
1376
|
"section",
|
|
@@ -996,7 +1386,7 @@ function createPublicBookingPlugin(options) {
|
|
|
996
1386
|
id: "booking",
|
|
997
1387
|
name: "Agendamento",
|
|
998
1388
|
icon: "CalendarCheck",
|
|
999
|
-
version: "0.
|
|
1389
|
+
version: "0.3.0",
|
|
1000
1390
|
scope: options.scope ?? "universal",
|
|
1001
1391
|
verticalId: options.verticalId,
|
|
1002
1392
|
scaffolds: ["website", "landing_page"],
|
|
@@ -1011,10 +1401,23 @@ function createPublicBookingPlugin(options) {
|
|
|
1011
1401
|
|
|
1012
1402
|
exports.BookingProvider = BookingProvider;
|
|
1013
1403
|
exports.BookingWidget = BookingWidget;
|
|
1404
|
+
exports.DAY_NAMES = DAY_NAMES;
|
|
1405
|
+
exports.DEFAULT_BOOKING_COMPONENTS = DEFAULT_BOOKING_COMPONENTS;
|
|
1406
|
+
exports.DefaultBrandHeader = DefaultBrandHeader;
|
|
1407
|
+
exports.DefaultServiceCard = DefaultServiceCard;
|
|
1408
|
+
exports.DefaultSidebarExtras = DefaultSidebarExtras;
|
|
1409
|
+
exports.DefaultStepRailItem = DefaultStepRailItem;
|
|
1410
|
+
exports.DefaultSuccessPanel = DefaultSuccessPanel;
|
|
1411
|
+
exports.WEEK_ORDER = WEEK_ORDER;
|
|
1412
|
+
exports.createMockPublicBookingProvider = createMockPublicBookingProvider;
|
|
1014
1413
|
exports.createPublicBookingPlugin = createPublicBookingPlugin;
|
|
1414
|
+
exports.createSupabasePublicBookingProvider = createSupabasePublicBookingProvider;
|
|
1415
|
+
exports.formatSlotRange = formatSlotRange;
|
|
1015
1416
|
exports.useAvailableSlots = useAvailableSlots;
|
|
1016
1417
|
exports.useBooking = useBooking;
|
|
1017
1418
|
exports.useCreateBooking = useCreateBooking;
|
|
1419
|
+
exports.useFormatters = useFormatters;
|
|
1420
|
+
exports.usePublicServices = usePublicServices;
|
|
1018
1421
|
exports.useServices = useServices;
|
|
1019
1422
|
//# sourceMappingURL=index.cjs.map
|
|
1020
1423
|
//# sourceMappingURL=index.cjs.map
|