@fayz-ai/plugin-agenda 0.2.0 → 0.2.1
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-GAJVIEXM.cjs +870 -0
- package/dist/chunk-GAJVIEXM.cjs.map +1 -0
- package/dist/chunk-U6VTLOHN.js +867 -0
- package/dist/chunk-U6VTLOHN.js.map +1 -0
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/index.cjs +16 -868
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -868
- package/dist/index.js.map +1 -1
- package/dist/public/BookingWidget.d.ts +9 -0
- package/dist/public/BookingWidget.d.ts.map +1 -0
- package/dist/public/context.d.ts +37 -0
- package/dist/public/context.d.ts.map +1 -0
- package/dist/public/createPublicBookingPlugin.d.ts +45 -0
- package/dist/public/createPublicBookingPlugin.d.ts.map +1 -0
- package/dist/public/hooks.d.ts +32 -0
- package/dist/public/hooks.d.ts.map +1 -0
- package/dist/public/index.cjs +1020 -0
- package/dist/public/index.cjs.map +1 -0
- package/dist/public/index.d.ts +9 -0
- package/dist/public/index.d.ts.map +1 -0
- package/dist/public/index.js +1012 -0
- package/dist/public/index.js.map +1 -0
- package/dist/public/types.d.ts +51 -0
- package/dist/public/types.d.ts.map +1 -0
- package/package.json +11 -5
- package/src/data/mock.ts +4 -1
- package/src/index.ts +3 -0
- package/src/integrations/google-calendar/integration.test.ts +1 -1
- package/src/public/BookingWidget.tsx +892 -0
- package/src/public/context.tsx +43 -0
- package/src/public/createPublicBookingPlugin.ts +155 -0
- package/src/public/hooks.ts +117 -0
- package/src/public/index.ts +15 -0
- package/src/public/types.ts +63 -0
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkGAJVIEXM_cjs = require('../chunk-GAJVIEXM.cjs');
|
|
4
|
+
require('../chunk-SYDBDI3Q.cjs');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var core = require('@fayz-ai/core');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
var lucideReact = require('lucide-react');
|
|
9
|
+
|
|
10
|
+
var PublicBookingContext = react.createContext(null);
|
|
11
|
+
function BookingProvider({ value, children }) {
|
|
12
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PublicBookingContext.Provider, { value, children });
|
|
13
|
+
}
|
|
14
|
+
function useBooking() {
|
|
15
|
+
const ctx = react.useContext(PublicBookingContext);
|
|
16
|
+
if (!ctx) {
|
|
17
|
+
throw new Error("[plugin-agenda] useBooking must be used within <BookingProvider>.");
|
|
18
|
+
}
|
|
19
|
+
return ctx;
|
|
20
|
+
}
|
|
21
|
+
function useServices() {
|
|
22
|
+
return useBooking().services;
|
|
23
|
+
}
|
|
24
|
+
function useAvailableSlots(params) {
|
|
25
|
+
const { provider, professionalId, services, window: window2 } = useBooking();
|
|
26
|
+
const [slots, setSlots] = react.useState([]);
|
|
27
|
+
const [loading, setLoading] = react.useState(false);
|
|
28
|
+
const [error, setError] = react.useState(null);
|
|
29
|
+
const { serviceId, date } = params;
|
|
30
|
+
const service = services.find((s) => s.id === serviceId);
|
|
31
|
+
const durationMinutes = service?.durationMinutes ?? 0;
|
|
32
|
+
react.useEffect(() => {
|
|
33
|
+
let active = true;
|
|
34
|
+
if (!serviceId || !date || !durationMinutes) {
|
|
35
|
+
setSlots([]);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
setLoading(true);
|
|
39
|
+
provider.getAvailableSlots({ assigneeId: professionalId, date, durationMinutes, slotInterval: window2.slotInterval }).then((result) => {
|
|
40
|
+
if (!active) return;
|
|
41
|
+
setSlots(result);
|
|
42
|
+
setError(null);
|
|
43
|
+
}).catch((err) => {
|
|
44
|
+
if (!active) return;
|
|
45
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
46
|
+
}).finally(() => {
|
|
47
|
+
if (active) setLoading(false);
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
active = false;
|
|
51
|
+
};
|
|
52
|
+
}, [provider, professionalId, serviceId, date, durationMinutes, window2.slotInterval]);
|
|
53
|
+
return { slots, loading, error };
|
|
54
|
+
}
|
|
55
|
+
function useCreateBooking() {
|
|
56
|
+
const { provider, professionalId, services } = useBooking();
|
|
57
|
+
const [submitting, setSubmitting] = react.useState(false);
|
|
58
|
+
const [error, setError] = react.useState(null);
|
|
59
|
+
const [done, setDone] = react.useState(false);
|
|
60
|
+
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
|
+
setSubmitting(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
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
|
+
});
|
|
85
|
+
setDone(true);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
88
|
+
setError(e);
|
|
89
|
+
throw e;
|
|
90
|
+
} finally {
|
|
91
|
+
setSubmitting(false);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { submit, submitting, error, done };
|
|
95
|
+
}
|
|
96
|
+
var DAY_NAMES = ["Domingo", "Segunda-feira", "Ter\xE7a-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "S\xE1bado"];
|
|
97
|
+
var WEEK_ORDER = [1, 2, 3, 4, 5, 6, 0];
|
|
98
|
+
function useFormatters(locale, currency) {
|
|
99
|
+
return react.useMemo(
|
|
100
|
+
() => ({
|
|
101
|
+
price: new Intl.NumberFormat(locale, { style: "currency", currency }),
|
|
102
|
+
time: new Intl.DateTimeFormat(locale, { hour: "2-digit", minute: "2-digit" }),
|
|
103
|
+
weekday: new Intl.DateTimeFormat(locale, { weekday: "short" }),
|
|
104
|
+
dayNum: new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit" }),
|
|
105
|
+
longDay: new Intl.DateTimeFormat(locale, { weekday: "long", day: "2-digit", month: "long" }),
|
|
106
|
+
dateShort: new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit", year: "numeric" }),
|
|
107
|
+
month: new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" })
|
|
108
|
+
}),
|
|
109
|
+
[locale, currency]
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function toIso(d) {
|
|
113
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
114
|
+
}
|
|
115
|
+
function readServiceParam() {
|
|
116
|
+
if (typeof window === "undefined") return null;
|
|
117
|
+
return new URLSearchParams(window.location.search).get("service");
|
|
118
|
+
}
|
|
119
|
+
function buildWeek(weekOffset) {
|
|
120
|
+
const base = /* @__PURE__ */ new Date();
|
|
121
|
+
base.setHours(0, 0, 0, 0);
|
|
122
|
+
const cells = [];
|
|
123
|
+
for (let i = 0; i < 7; i++) {
|
|
124
|
+
const d = new Date(base.getFullYear(), base.getMonth(), base.getDate() + weekOffset * 7 + i);
|
|
125
|
+
cells.push({ iso: toIso(d), date: d, isToday: weekOffset === 0 && i === 0 });
|
|
126
|
+
}
|
|
127
|
+
return cells;
|
|
128
|
+
}
|
|
129
|
+
function formatSlotRange(startIso, durationMin, fmt) {
|
|
130
|
+
const start = new Date(startIso);
|
|
131
|
+
const end = new Date(start.getTime() + durationMin * 6e4);
|
|
132
|
+
return `${fmt.dateShort.format(start)} \u2013 ${fmt.time.format(start)} at\xE9 ${fmt.time.format(end)} (${durationMin}min)`;
|
|
133
|
+
}
|
|
134
|
+
var PIX_PAYLOAD = "00020126840014br.gov.bcb.pix2562qrcode.hempdent.com.br/pix/v2/9d8f7a6b-consulta-canabica5204000053039865802BR5913HEMPDENT6009SAO PAULO62070503***6304AB12";
|
|
135
|
+
function QrPlaceholder({ data }) {
|
|
136
|
+
const size = 25;
|
|
137
|
+
let seed = 0;
|
|
138
|
+
for (let i = 0; i < data.length; i++) seed = (seed * 31 + data.charCodeAt(i)) % 1e5;
|
|
139
|
+
const cells = [];
|
|
140
|
+
const isFinder = (x, y) => {
|
|
141
|
+
const inBox = (bx, by) => x >= bx && x < bx + 7 && y >= by && y < by + 7;
|
|
142
|
+
return inBox(0, 0) || inBox(size - 7, 0) || inBox(0, size - 7);
|
|
143
|
+
};
|
|
144
|
+
for (let y = 0; y < size; y++) {
|
|
145
|
+
for (let x = 0; x < size; x++) {
|
|
146
|
+
if (isFinder(x, y)) {
|
|
147
|
+
const local = (bx, by) => ({ lx: x - bx, ly: y - by });
|
|
148
|
+
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 { lx, ly } = local(corner.bx, corner.by);
|
|
150
|
+
const outer = lx === 0 || lx === 6 || ly === 0 || ly === 6;
|
|
151
|
+
const inner = lx >= 2 && lx <= 4 && ly >= 2 && ly <= 4;
|
|
152
|
+
if (outer || inner) cells.push({ x, y });
|
|
153
|
+
} else if ((x * 31 + y * 17 + x * y + seed) % 3 === 0) {
|
|
154
|
+
cells.push({ x, y });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: `0 0 ${size} ${size}`, className: "h-40 w-40", role: "img", "aria-label": "QR Code Pix (demonstra\xE7\xE3o)", children: [
|
|
159
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: size, height: size, fill: "white" }),
|
|
160
|
+
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
|
+
] });
|
|
162
|
+
}
|
|
163
|
+
function BrandHeader() {
|
|
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({
|
|
176
|
+
service,
|
|
177
|
+
featured,
|
|
178
|
+
onSelect,
|
|
179
|
+
fmt
|
|
180
|
+
}) {
|
|
181
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
182
|
+
"button",
|
|
183
|
+
{
|
|
184
|
+
type: "button",
|
|
185
|
+
onClick: onSelect,
|
|
186
|
+
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",
|
|
187
|
+
children: [
|
|
188
|
+
/* @__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(lucideReact.Video, { className: "h-5 w-5" }) }),
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
190
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
191
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: service.name }),
|
|
192
|
+
featured ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-accent px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary", children: "Mais procurado" }) : null
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mt-0.5 flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
196
|
+
" ",
|
|
197
|
+
service.durationMinutes,
|
|
198
|
+
" min \xB7 Online"
|
|
199
|
+
] }),
|
|
200
|
+
service.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-sm text-muted-foreground", children: service.description }) : null
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-right", children: [
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block font-semibold tabular-nums text-primary", children: fmt.price.format(service.price) }),
|
|
204
|
+
/* @__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" })
|
|
205
|
+
] })
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
function StepRail({
|
|
211
|
+
n,
|
|
212
|
+
label,
|
|
213
|
+
state,
|
|
214
|
+
children
|
|
215
|
+
}) {
|
|
216
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "relative pl-11", children: [
|
|
217
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "absolute left-[15px] top-8 bottom-[-18px] w-px bg-border last:hidden" }),
|
|
218
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
219
|
+
"span",
|
|
220
|
+
{
|
|
221
|
+
className: [
|
|
222
|
+
"absolute left-0 top-0 flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition-colors",
|
|
223
|
+
state === "done" ? "bg-primary text-primary-foreground" : state === "active" ? "bg-accent text-primary ring-2 ring-primary" : "bg-muted text-muted-foreground"
|
|
224
|
+
].join(" "),
|
|
225
|
+
children: state === "done" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) : n
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `pt-1 text-sm font-semibold ${state === "todo" ? "text-muted-foreground" : "text-foreground"}`, children: label }),
|
|
229
|
+
children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children }) : null
|
|
230
|
+
] });
|
|
231
|
+
}
|
|
232
|
+
function BookingWidget() {
|
|
233
|
+
const {
|
|
234
|
+
services,
|
|
235
|
+
professionalName,
|
|
236
|
+
businessHours,
|
|
237
|
+
payment,
|
|
238
|
+
paymentProvider,
|
|
239
|
+
onIdentityVerified,
|
|
240
|
+
labels,
|
|
241
|
+
window: bookingWindow,
|
|
242
|
+
locale,
|
|
243
|
+
currency
|
|
244
|
+
} = useBooking();
|
|
245
|
+
const fmt = useFormatters(locale, currency);
|
|
246
|
+
const preselected = react.useMemo(() => {
|
|
247
|
+
const q = readServiceParam();
|
|
248
|
+
if (q && services.some((s) => s.id === q)) return q;
|
|
249
|
+
return null;
|
|
250
|
+
}, [services]);
|
|
251
|
+
const todayIso = react.useMemo(() => toIso(/* @__PURE__ */ new Date()), []);
|
|
252
|
+
const [step, setStep] = react.useState(preselected ? "datetime" : "overview");
|
|
253
|
+
const [serviceId, setServiceId] = react.useState(preselected);
|
|
254
|
+
const [weekOffset, setWeekOffset] = react.useState(0);
|
|
255
|
+
const [date, setDate] = react.useState(todayIso);
|
|
256
|
+
const [slotStart, setSlotStart] = react.useState(null);
|
|
257
|
+
const [contact, setContact] = react.useState({ name: "", phone: "", email: "", notes: "" });
|
|
258
|
+
const [contactStep, setContactStep] = react.useState("phone");
|
|
259
|
+
const [code, setCode] = react.useState("");
|
|
260
|
+
const [codeError, setCodeError] = react.useState(false);
|
|
261
|
+
const [paymentStep, setPaymentStep] = react.useState("choose");
|
|
262
|
+
const [copied, setCopied] = react.useState(false);
|
|
263
|
+
const [countryIso, setCountryIso] = react.useState(core.DEFAULT_COUNTRY);
|
|
264
|
+
const country = core.getCountry(countryIso);
|
|
265
|
+
const [charge, setCharge] = react.useState(null);
|
|
266
|
+
const [charging, setCharging] = react.useState(false);
|
|
267
|
+
const pollRef = react.useRef(null);
|
|
268
|
+
react.useEffect(() => {
|
|
269
|
+
if (!(step === "payment" && paymentStep === "pix" && charge && paymentProvider)) return;
|
|
270
|
+
let active = true;
|
|
271
|
+
const clear = () => {
|
|
272
|
+
if (pollRef.current) {
|
|
273
|
+
clearInterval(pollRef.current);
|
|
274
|
+
pollRef.current = null;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const tick = () => {
|
|
278
|
+
paymentProvider.getChargeStatus(charge.chargeId).then((s) => {
|
|
279
|
+
if (!active) return;
|
|
280
|
+
if (s === "paid") {
|
|
281
|
+
clear();
|
|
282
|
+
setStep("success");
|
|
283
|
+
} else if (s === "expired" || s === "failed") clear();
|
|
284
|
+
}).catch(() => {
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
tick();
|
|
288
|
+
pollRef.current = setInterval(tick, 2500);
|
|
289
|
+
return () => {
|
|
290
|
+
active = false;
|
|
291
|
+
clear();
|
|
292
|
+
};
|
|
293
|
+
}, [step, paymentStep, charge, paymentProvider]);
|
|
294
|
+
const week = react.useMemo(() => buildWeek(weekOffset), [weekOffset]);
|
|
295
|
+
const { slots, loading: slotsLoading } = useAvailableSlots({ serviceId, date });
|
|
296
|
+
const { submit, submitting, error } = useCreateBooking();
|
|
297
|
+
const minTime = Date.now() + bookingWindow.minAdvanceHours * 36e5;
|
|
298
|
+
const bookableSlots = slots.filter((s) => new Date(s.start).getTime() >= minTime);
|
|
299
|
+
const service = services.find((s) => s.id === serviceId) ?? null;
|
|
300
|
+
const maxWeekOffset = Math.floor(bookingWindow.maxAdvanceDays / 7);
|
|
301
|
+
const openDays = new Set(businessHours.daysOfWeek);
|
|
302
|
+
function startBooking(id) {
|
|
303
|
+
setServiceId(id ?? serviceId ?? services[0]?.id ?? null);
|
|
304
|
+
setDate(todayIso);
|
|
305
|
+
setSlotStart(null);
|
|
306
|
+
setStep("datetime");
|
|
307
|
+
}
|
|
308
|
+
async function handleConfirm() {
|
|
309
|
+
if (!serviceId || !slotStart || !contact.name.trim()) return;
|
|
310
|
+
try {
|
|
311
|
+
await submit({ serviceId, startsAt: slotStart, contact });
|
|
312
|
+
setStep("success");
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function emitIdentity() {
|
|
317
|
+
onIdentityVerified?.({
|
|
318
|
+
name: contact.name,
|
|
319
|
+
phone: `${country.dial} ${contact.phone}`,
|
|
320
|
+
email: contact.email || void 0
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function sendCode() {
|
|
324
|
+
if (core.unmaskPhone(contact.phone).length < 8) return;
|
|
325
|
+
setCode("");
|
|
326
|
+
setCodeError(false);
|
|
327
|
+
setContactStep("code");
|
|
328
|
+
}
|
|
329
|
+
function confirmCode() {
|
|
330
|
+
if (code.trim() === "0000") {
|
|
331
|
+
setCodeError(false);
|
|
332
|
+
emitIdentity();
|
|
333
|
+
setContactStep("details");
|
|
334
|
+
} else {
|
|
335
|
+
setCodeError(true);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function submitDetails() {
|
|
339
|
+
if (!contact.name.trim() || submitting) return;
|
|
340
|
+
emitIdentity();
|
|
341
|
+
if (payment) {
|
|
342
|
+
setPaymentStep("choose");
|
|
343
|
+
setStep("payment");
|
|
344
|
+
} else void handleConfirm();
|
|
345
|
+
}
|
|
346
|
+
async function payWithPix(fee, svcId, svcName) {
|
|
347
|
+
if (charging) return;
|
|
348
|
+
setCharging(true);
|
|
349
|
+
try {
|
|
350
|
+
await submit({ serviceId: svcId, startsAt: slotStart, contact });
|
|
351
|
+
if (paymentProvider) {
|
|
352
|
+
const c = await paymentProvider.createCharge({
|
|
353
|
+
amount: fee,
|
|
354
|
+
currency,
|
|
355
|
+
method: "pix",
|
|
356
|
+
description: svcName,
|
|
357
|
+
customer: { name: contact.name, email: contact.email || void 0, phone: contact.phone || void 0 }
|
|
358
|
+
});
|
|
359
|
+
setCharge(c);
|
|
360
|
+
}
|
|
361
|
+
setPaymentStep("pix");
|
|
362
|
+
} catch {
|
|
363
|
+
} finally {
|
|
364
|
+
setCharging(false);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (step === "overview") {
|
|
368
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-4xl", children: [
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx(BrandHeader, {}),
|
|
370
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_18rem]", children: [
|
|
371
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
|
|
372
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "rounded-2xl border border-border bg-card p-6 shadow-sm", children: [
|
|
373
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-4 font-heading text-lg font-bold text-foreground", children: "Servi\xE7os" }),
|
|
374
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: services.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(ServiceRow, { service: s, featured: i === 0, onSelect: () => startBooking(s.id), fmt }, s.id)) })
|
|
375
|
+
] }),
|
|
376
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "rounded-2xl border border-border bg-card p-6 shadow-sm", children: [
|
|
377
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-lg font-bold text-foreground", children: "Sobre" }),
|
|
378
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-3 mb-2 flex items-center gap-1.5 text-sm font-medium text-muted-foreground", children: [
|
|
379
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4" }),
|
|
380
|
+
" Hor\xE1rio de funcionamento"
|
|
381
|
+
] }),
|
|
382
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-border border-l-2 border-primary/20 pl-4", children: WEEK_ORDER.map((day) => {
|
|
383
|
+
const open = openDays.has(day);
|
|
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
|
+
] })
|
|
422
|
+
] });
|
|
423
|
+
}
|
|
424
|
+
const railState = {
|
|
425
|
+
service: "done",
|
|
426
|
+
professional: "done",
|
|
427
|
+
datetime: slotStart ? "done" : step === "datetime" ? "active" : "todo",
|
|
428
|
+
contact: step === "payment" || step === "success" ? "done" : step === "contact" ? "active" : "todo",
|
|
429
|
+
payment: step === "success" ? "done" : step === "payment" ? "active" : "todo"
|
|
430
|
+
};
|
|
431
|
+
const railTitle = step === "payment" && paymentStep === "pix" ? "Aguardando pagamento" : "Passos para agendar";
|
|
432
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-4xl", children: [
|
|
433
|
+
/* @__PURE__ */ jsxRuntime.jsx(BrandHeader, {}),
|
|
434
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_18rem]", children: [
|
|
435
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl border border-border bg-card p-6 md:p-7 shadow-sm", children: [
|
|
436
|
+
/* @__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)) }),
|
|
437
|
+
step !== "success" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
438
|
+
"button",
|
|
439
|
+
{
|
|
440
|
+
type: "button",
|
|
441
|
+
onClick: () => setStep(step === "payment" ? "contact" : step === "contact" ? "datetime" : "overview"),
|
|
442
|
+
className: "mb-4 inline-flex items-center gap-1 rounded text-sm font-medium text-muted-foreground hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
443
|
+
children: [
|
|
444
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" }),
|
|
445
|
+
" ",
|
|
446
|
+
step === "datetime" ? "Servi\xE7os" : "Voltar"
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
) : null,
|
|
450
|
+
step === "datetime" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
451
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [
|
|
452
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-heading text-xl font-bold text-foreground", children: labels.dateStep }),
|
|
453
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
454
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3.5 w-3.5" }),
|
|
455
|
+
" Fuso: Hor\xE1rio de Bras\xEDlia"
|
|
456
|
+
] })
|
|
457
|
+
] }),
|
|
458
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex items-center justify-between", children: [
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold capitalize text-foreground", children: fmt.month.format(week[0].date) }),
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
461
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
462
|
+
"button",
|
|
463
|
+
{
|
|
464
|
+
type: "button",
|
|
465
|
+
disabled: weekOffset === 0,
|
|
466
|
+
onClick: () => setWeekOffset((w) => Math.max(0, w - 1)),
|
|
467
|
+
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",
|
|
468
|
+
"aria-label": "Semana anterior",
|
|
469
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
|
|
470
|
+
}
|
|
471
|
+
),
|
|
472
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
473
|
+
"button",
|
|
474
|
+
{
|
|
475
|
+
type: "button",
|
|
476
|
+
disabled: weekOffset >= maxWeekOffset,
|
|
477
|
+
onClick: () => setWeekOffset((w) => Math.min(maxWeekOffset, w + 1)),
|
|
478
|
+
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",
|
|
479
|
+
"aria-label": "Pr\xF3xima semana",
|
|
480
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
] })
|
|
484
|
+
] }),
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 grid grid-cols-7 gap-1.5", children: week.map((d) => {
|
|
486
|
+
const selected = date === d.iso;
|
|
487
|
+
const closed = !openDays.has(d.date.getDay());
|
|
488
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
489
|
+
"button",
|
|
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
|
+
)
|
|
719
|
+
] }),
|
|
720
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
721
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Reenviar c\xF3digo via WhatsApp em 28 segundos." }),
|
|
722
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setContactStep("phone"), className: "font-medium text-primary hover:underline", children: "Voltar" })
|
|
723
|
+
] })
|
|
724
|
+
] }) }) : null,
|
|
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
|
|
905
|
+
] })
|
|
906
|
+
] }) })
|
|
907
|
+
] })
|
|
908
|
+
] });
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/public/createPublicBookingPlugin.ts
|
|
912
|
+
var DEFAULT_LABELS = {
|
|
913
|
+
title: "Agende sua consulta",
|
|
914
|
+
serviceStep: "Escolha o servi\xE7o",
|
|
915
|
+
dateStep: "Escolha a data",
|
|
916
|
+
contactStep: "Seus dados",
|
|
917
|
+
confirmCta: "Confirmar agendamento",
|
|
918
|
+
successTitle: "Agendamento confirmado!",
|
|
919
|
+
successBody: "Voc\xEA receber\xE1 os detalhes em breve. At\xE9 logo!"
|
|
920
|
+
};
|
|
921
|
+
var DEFAULT_WINDOW = { minAdvanceHours: 2, maxAdvanceDays: 21, slotInterval: 30 };
|
|
922
|
+
var DEFAULT_HOURS = { daysOfWeek: [1, 2, 3, 4, 5, 6], start: "09:00", end: "18:00" };
|
|
923
|
+
var scheduleId = 0;
|
|
924
|
+
function buildSchedules(professionalId, hours) {
|
|
925
|
+
return hours.daysOfWeek.map((day) => ({
|
|
926
|
+
id: `sched-${professionalId}-${day}-${scheduleId++}`,
|
|
927
|
+
tenantId: "public",
|
|
928
|
+
kind: "working_hours",
|
|
929
|
+
assigneeId: professionalId,
|
|
930
|
+
locationId: null,
|
|
931
|
+
dayOfWeek: day,
|
|
932
|
+
specificDate: null,
|
|
933
|
+
startsAt: hours.start,
|
|
934
|
+
endsAt: hours.end,
|
|
935
|
+
isActive: true,
|
|
936
|
+
metadata: {}
|
|
937
|
+
}));
|
|
938
|
+
}
|
|
939
|
+
function createPublicBookingPlugin(options) {
|
|
940
|
+
const basePath = options.basePath ?? "/agendar";
|
|
941
|
+
const professionalId = options.professional?.id ?? "dr-hiago";
|
|
942
|
+
const professionalName = options.professional?.name ?? "Especialista";
|
|
943
|
+
const window2 = { ...DEFAULT_WINDOW, ...options.window };
|
|
944
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
945
|
+
const currency = options.currency ?? "BRL";
|
|
946
|
+
const locale = options.locale ?? "pt-BR";
|
|
947
|
+
const hours = options.workingHours ?? DEFAULT_HOURS;
|
|
948
|
+
const payment = options.payment?.enabled ? {
|
|
949
|
+
reservationFeePercent: options.payment.reservationFeePercent ?? 100,
|
|
950
|
+
method: options.payment.method ?? "pix"
|
|
951
|
+
} : null;
|
|
952
|
+
const professional = {
|
|
953
|
+
id: professionalId,
|
|
954
|
+
name: professionalName,
|
|
955
|
+
avatarUrl: null,
|
|
956
|
+
locationId: null,
|
|
957
|
+
locationName: null,
|
|
958
|
+
isActive: true
|
|
959
|
+
};
|
|
960
|
+
const mockSeed = options.seed ?? {
|
|
961
|
+
professionals: [professional],
|
|
962
|
+
schedules: buildSchedules(professionalId, hours),
|
|
963
|
+
bookings: []
|
|
964
|
+
};
|
|
965
|
+
const provider = options.dataProvider ?? core.createSafeDataProvider(
|
|
966
|
+
() => chunkGAJVIEXM_cjs.createSupabaseAgendaProvider(),
|
|
967
|
+
() => chunkGAJVIEXM_cjs.createMockAgendaProvider({ seed: mockSeed })
|
|
968
|
+
);
|
|
969
|
+
const value = {
|
|
970
|
+
provider,
|
|
971
|
+
professionalId,
|
|
972
|
+
professionalName,
|
|
973
|
+
services: options.services,
|
|
974
|
+
businessHours: hours,
|
|
975
|
+
payment,
|
|
976
|
+
paymentProvider: options.paymentProvider ?? null,
|
|
977
|
+
onIdentityVerified: options.onIdentityVerified ?? null,
|
|
978
|
+
window: window2,
|
|
979
|
+
labels,
|
|
980
|
+
currency,
|
|
981
|
+
locale
|
|
982
|
+
};
|
|
983
|
+
const Provider = ({ children }) => react.createElement(BookingProvider, { value, children });
|
|
984
|
+
Provider.displayName = "PublicBookingProvider";
|
|
985
|
+
const BookingScreen = () => react.createElement(
|
|
986
|
+
"section",
|
|
987
|
+
{ className: "py-16 bg-background" },
|
|
988
|
+
react.createElement(
|
|
989
|
+
"div",
|
|
990
|
+
{ className: "container mx-auto px-6" },
|
|
991
|
+
react.createElement(BookingWidget)
|
|
992
|
+
)
|
|
993
|
+
);
|
|
994
|
+
BookingScreen.displayName = "BookingScreen";
|
|
995
|
+
const manifest = {
|
|
996
|
+
id: "booking",
|
|
997
|
+
name: "Agendamento",
|
|
998
|
+
icon: "CalendarCheck",
|
|
999
|
+
version: "0.1.0",
|
|
1000
|
+
scope: options.scope ?? "universal",
|
|
1001
|
+
verticalId: options.verticalId,
|
|
1002
|
+
scaffolds: ["website", "landing_page"],
|
|
1003
|
+
defaultEnabled: true,
|
|
1004
|
+
dependencies: [],
|
|
1005
|
+
navigation: [],
|
|
1006
|
+
routes: [{ path: basePath, component: BookingScreen, guard: "public" }],
|
|
1007
|
+
widgets: []
|
|
1008
|
+
};
|
|
1009
|
+
return { manifest, Provider, dataProvider: provider };
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
exports.BookingProvider = BookingProvider;
|
|
1013
|
+
exports.BookingWidget = BookingWidget;
|
|
1014
|
+
exports.createPublicBookingPlugin = createPublicBookingPlugin;
|
|
1015
|
+
exports.useAvailableSlots = useAvailableSlots;
|
|
1016
|
+
exports.useBooking = useBooking;
|
|
1017
|
+
exports.useCreateBooking = useCreateBooking;
|
|
1018
|
+
exports.useServices = useServices;
|
|
1019
|
+
//# sourceMappingURL=index.cjs.map
|
|
1020
|
+
//# sourceMappingURL=index.cjs.map
|