@bookr-cl/widget 0.0.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/index.cjs ADDED
@@ -0,0 +1,728 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DAY_NAMES: () => DAY_NAMES,
24
+ DAY_NAMES_FULL: () => DAY_NAMES_FULL,
25
+ DEFAULT_PROVIDER_COLORS: () => DEFAULT_PROVIDER_COLORS,
26
+ clearClientToken: () => clearClientToken,
27
+ createBooking: () => createBooking,
28
+ createClient: () => createClient,
29
+ dateStr: () => dateStr,
30
+ fetchMe: () => fetchMe,
31
+ formatMonthRange: () => formatMonthRange,
32
+ getAvailableSlots: () => getAvailableSlots,
33
+ getClientToken: () => getClientToken,
34
+ getMe: () => getMe,
35
+ getMondayOf: () => getMondayOf,
36
+ getMyBookings: () => getMyBookings,
37
+ getMyPackages: () => getMyPackages,
38
+ listLocations: () => listLocations,
39
+ listProviders: () => listProviders,
40
+ listServices: () => listServices,
41
+ loginWithGoogle: () => loginWithGoogle,
42
+ register: () => register,
43
+ sendMagicLink: () => sendMagicLink,
44
+ setClientToken: () => setClientToken,
45
+ useBookingFlow: () => useBookingFlow,
46
+ useClassSchedule: () => useClassSchedule,
47
+ useClientAccount: () => useClientAccount,
48
+ useMagicLinkAuth: () => useMagicLinkAuth,
49
+ useRegisterForm: () => useRegisterForm,
50
+ verifyMagicLink: () => verifyMagicLink
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // src/api.ts
55
+ function headers(cfg, withJson = false) {
56
+ const h = { "X-API-Key": cfg.apiKey };
57
+ if (withJson) h["Content-Type"] = "application/json";
58
+ return h;
59
+ }
60
+ function authHeaders(token, withJson = false) {
61
+ const h = { Authorization: `Bearer ${token}` };
62
+ if (withJson) h["Content-Type"] = "application/json";
63
+ return h;
64
+ }
65
+ async function listLocations(cfg) {
66
+ const res = await fetch(`${cfg.apiBase}/v1/locations`, { headers: headers(cfg) });
67
+ const data = await res.json();
68
+ return data.data || [];
69
+ }
70
+ async function listServices(cfg, perPage = 100) {
71
+ const res = await fetch(`${cfg.apiBase}/v1/services?per_page=${perPage}`, { headers: headers(cfg) });
72
+ const data = await res.json();
73
+ return data.data || [];
74
+ }
75
+ async function listProviders(cfg, perPage = 100) {
76
+ const res = await fetch(`${cfg.apiBase}/v1/providers?per_page=${perPage}`, { headers: headers(cfg) });
77
+ const data = await res.json();
78
+ return data.data || [];
79
+ }
80
+ async function getAvailableSlots(cfg, params) {
81
+ const q = new URLSearchParams({ location_id: params.locationId, start_date: params.startDate });
82
+ if (params.serviceId) q.set("service_id", params.serviceId);
83
+ if (params.providerId) q.set("provider_id", params.providerId);
84
+ const res = await fetch(`${cfg.apiBase}/v1/available-slots?${q.toString()}`, { headers: headers(cfg) });
85
+ const data = await res.json();
86
+ return data.data?.slots || [];
87
+ }
88
+ async function createClient(cfg, form) {
89
+ const res = await fetch(`${cfg.apiBase}/v1/clients`, {
90
+ method: "POST",
91
+ headers: headers(cfg, true),
92
+ body: JSON.stringify({
93
+ first_name: form.firstName,
94
+ last_name: form.lastName,
95
+ email: form.email,
96
+ phone: form.phone,
97
+ rut: form.rut || void 0
98
+ })
99
+ });
100
+ if (!res.ok) throw new Error("Error registrando datos");
101
+ return res.json();
102
+ }
103
+ async function createBooking(cfg, input, clientToken) {
104
+ const reqHeaders = headers(cfg, true);
105
+ if (clientToken) reqHeaders.Authorization = `Bearer ${clientToken}`;
106
+ const res = await fetch(`${cfg.apiBase}/v1/bookings`, {
107
+ method: "POST",
108
+ headers: reqHeaders,
109
+ body: JSON.stringify(input)
110
+ });
111
+ if (res.status === 409) {
112
+ const data = await res.json().catch(() => ({}));
113
+ return { ok: false, conflict: true, message: data.detail || "Ese horario ya no est\xE1 disponible." };
114
+ }
115
+ if (!res.ok) {
116
+ return { ok: false, conflict: false, message: "Error creando la reserva. Por favor intenta nuevamente." };
117
+ }
118
+ return { ok: true, booking: await res.json() };
119
+ }
120
+ async function sendMagicLink(cfg, email, frontendUrl, profile) {
121
+ const res = await fetch(`${cfg.apiBase}/v1/client-auth/magic-link`, {
122
+ method: "POST",
123
+ headers: headers(cfg, true),
124
+ body: JSON.stringify({
125
+ email: email.trim().toLowerCase(),
126
+ frontend_url: frontendUrl,
127
+ first_name: profile?.firstName,
128
+ last_name: profile?.lastName,
129
+ phone: profile?.phone,
130
+ rut: profile?.rut
131
+ })
132
+ });
133
+ const data = await res.json();
134
+ if (!res.ok) throw new Error(data.detail || "Error enviando el link.");
135
+ }
136
+ async function verifyMagicLink(cfg, token) {
137
+ const res = await fetch(`${cfg.apiBase}/v1/client-auth/magic-link/verify`, {
138
+ method: "POST",
139
+ headers: headers(cfg, true),
140
+ body: JSON.stringify({ token })
141
+ });
142
+ const data = await res.json();
143
+ if (!data.access_token) throw new Error(data.detail || "Link inv\xE1lido.");
144
+ return data;
145
+ }
146
+ async function loginWithGoogle(cfg, credential) {
147
+ const res = await fetch(`${cfg.apiBase}/v1/client-auth/google`, {
148
+ method: "POST",
149
+ headers: headers(cfg, true),
150
+ body: JSON.stringify({ credential })
151
+ });
152
+ const data = await res.json();
153
+ if (!res.ok) throw new Error(data.detail || "Error con Google.");
154
+ return data;
155
+ }
156
+ async function register(cfg, form) {
157
+ const res = await fetch(`${cfg.apiBase}/v1/client-auth/register`, {
158
+ method: "POST",
159
+ headers: headers(cfg, true),
160
+ body: JSON.stringify({
161
+ first_name: form.firstName,
162
+ last_name: form.lastName,
163
+ email: form.email,
164
+ phone: form.phone || void 0,
165
+ password: form.password
166
+ })
167
+ });
168
+ const data = await res.json();
169
+ if (!res.ok) throw new Error(data.detail || "Error al registrarse.");
170
+ return data;
171
+ }
172
+ async function getMe(apiBase, token) {
173
+ const res = await fetch(`${apiBase}/v1/me`, { headers: authHeaders(token) });
174
+ return res.json();
175
+ }
176
+ async function getMyBookings(apiBase, token, params) {
177
+ const q = new URLSearchParams();
178
+ if (params?.upcoming) q.set("upcoming", "true");
179
+ q.set("per_page", String(params?.perPage ?? 20));
180
+ const res = await fetch(`${apiBase}/v1/me/bookings?${q.toString()}`, { headers: authHeaders(token) });
181
+ const data = await res.json();
182
+ return data.data || [];
183
+ }
184
+ async function getMyPackages(apiBase, token) {
185
+ const res = await fetch(`${apiBase}/v1/me/packages`, { headers: authHeaders(token) });
186
+ const data = await res.json();
187
+ return data.data || [];
188
+ }
189
+
190
+ // src/clientAuth.ts
191
+ var TOKEN_KEY = "client_token";
192
+ function getClientToken() {
193
+ if (typeof window === "undefined") return null;
194
+ return localStorage.getItem(TOKEN_KEY);
195
+ }
196
+ function setClientToken(token) {
197
+ if (typeof window === "undefined") return;
198
+ localStorage.setItem(TOKEN_KEY, token);
199
+ }
200
+ function clearClientToken() {
201
+ if (typeof window === "undefined") return;
202
+ localStorage.removeItem(TOKEN_KEY);
203
+ }
204
+ async function fetchMe(apiBase, token) {
205
+ try {
206
+ return await getMe(apiBase, token);
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+
212
+ // src/dateUtils.ts
213
+ function getMondayOf(date) {
214
+ const d = new Date(date);
215
+ const day = (d.getDay() + 6) % 7;
216
+ d.setDate(d.getDate() - day);
217
+ d.setHours(0, 0, 0, 0);
218
+ return d;
219
+ }
220
+ function dateStr(d) {
221
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
222
+ }
223
+ function formatMonthRange(monday) {
224
+ const sunday = new Date(monday);
225
+ sunday.setDate(monday.getDate() + 6);
226
+ const opts = { day: "numeric", month: "long" };
227
+ const start = monday.toLocaleDateString("es-CL", opts);
228
+ const end = sunday.toLocaleDateString("es-CL", opts);
229
+ return `${start} \u2013 ${end}`;
230
+ }
231
+ var DAY_NAMES = ["Lun", "Mar", "Mi\xE9", "Jue", "Vie", "S\xE1b", "Dom"];
232
+ var DAY_NAMES_FULL = ["Lunes", "Martes", "Mi\xE9rcoles", "Jueves", "Viernes", "S\xE1bado", "Domingo"];
233
+ var DEFAULT_PROVIDER_COLORS = [
234
+ { bg: "#e8f5f0", border: "#2bbf8e", text: "#1a7a5a" },
235
+ { bg: "#eef0fb", border: "#6b7de8", text: "#3d4fa8" },
236
+ { bg: "#fdf3e8", border: "#e8a24b", text: "#a0600a" },
237
+ { bg: "#fdeef4", border: "#e86b9a", text: "#a03060" }
238
+ ];
239
+
240
+ // src/hooks/useBookingFlow.ts
241
+ var import_react = require("react");
242
+ var EMPTY_FORM = { firstName: "", lastName: "", email: "", phone: "", rut: "" };
243
+ function validateClientForm(form) {
244
+ if (!form.firstName.trim()) return "Por favor ingresa tu nombre.";
245
+ if (!form.lastName.trim()) return "Por favor ingresa tu apellido.";
246
+ if (!form.email.trim() || !/\S+@\S+\.\S+/.test(form.email)) return "Por favor ingresa un email v\xE1lido.";
247
+ if (!form.phone.trim()) return "Por favor ingresa tu tel\xE9fono.";
248
+ if (!form.rut.trim()) return "Por favor ingresa tu RUT.";
249
+ return null;
250
+ }
251
+ function buildBookingPayload(args) {
252
+ const { form, client, selectedDate, selectedSlot, selectedSvc, selectedLoc } = args;
253
+ const price = args.overrides?.price ?? args.svcPrice ?? "0";
254
+ const notes = args.overrides?.notes ?? args.defaultNotes;
255
+ return {
256
+ start_time: `${selectedDate}T${selectedSlot.start_time}:00-04:00`,
257
+ end_time: `${selectedDate}T${selectedSlot.end_time}:00-04:00`,
258
+ service_id: selectedSvc,
259
+ service_provider_id: selectedSlot.provider_id,
260
+ location_id: selectedLoc,
261
+ price,
262
+ notes,
263
+ // Ignored server-side unless a client/staff auth token proves the caller's
264
+ // identity; for a logged-in client it's redundant but harmless to include.
265
+ client_id: client?.id,
266
+ client_first_name: form.firstName,
267
+ client_last_name: form.lastName,
268
+ client_email: form.email,
269
+ client_phone: form.phone,
270
+ client_rut: form.rut || void 0
271
+ };
272
+ }
273
+ function useBookingFlow(opts) {
274
+ const [locations, setLocations] = (0, import_react.useState)([]);
275
+ const [services, setServices] = (0, import_react.useState)([]);
276
+ const [providers, setProviders] = (0, import_react.useState)([]);
277
+ const [slots, setSlots] = (0, import_react.useState)([]);
278
+ const [selectedLoc, setSelectedLoc] = (0, import_react.useState)("");
279
+ const [selectedSvc, setSelectedSvc] = (0, import_react.useState)("");
280
+ const [selectedProv, setSelectedProv] = (0, import_react.useState)("");
281
+ const [selectedDate, setSelectedDate] = (0, import_react.useState)("");
282
+ const [selectedSlot, setSelectedSlot] = (0, import_react.useState)(null);
283
+ const [form, setForm] = (0, import_react.useState)(EMPTY_FORM);
284
+ const [loading, setLoading] = (0, import_react.useState)(false);
285
+ const [error, setError] = (0, import_react.useState)("");
286
+ const [client, setClient] = (0, import_react.useState)(null);
287
+ (0, import_react.useEffect)(() => {
288
+ const token = getClientToken();
289
+ if (!token) return;
290
+ fetchMe(opts.apiBase, token).then((profile) => {
291
+ if (!profile) return;
292
+ setClient(profile);
293
+ setForm((f) => ({
294
+ ...f,
295
+ firstName: profile.first_name || f.firstName,
296
+ lastName: profile.last_name || f.lastName,
297
+ email: profile.email || f.email,
298
+ phone: profile.phone || f.phone
299
+ }));
300
+ });
301
+ }, []);
302
+ (0, import_react.useEffect)(() => {
303
+ listLocations(opts).then(setLocations).catch(() => setError("Error cargando sucursales"));
304
+ }, []);
305
+ (0, import_react.useEffect)(() => {
306
+ if (!selectedLoc) return;
307
+ listServices(opts).then(setServices).catch(() => setError("Error cargando servicios"));
308
+ }, [selectedLoc]);
309
+ (0, import_react.useEffect)(() => {
310
+ if (!selectedSvc) return;
311
+ listProviders(opts).then(setProviders).catch(() => setError("Error cargando profesionales"));
312
+ }, [selectedSvc]);
313
+ (0, import_react.useEffect)(() => {
314
+ if (!selectedLoc || !selectedDate) return;
315
+ setLoading(true);
316
+ getAvailableSlots(opts, {
317
+ locationId: selectedLoc,
318
+ startDate: selectedDate,
319
+ serviceId: selectedSvc || void 0,
320
+ providerId: selectedProv || void 0
321
+ }).then((s) => {
322
+ setSlots(s);
323
+ setLoading(false);
324
+ }).catch(() => {
325
+ setError("Error cargando horarios");
326
+ setLoading(false);
327
+ });
328
+ }, [selectedLoc, selectedSvc, selectedProv, selectedDate]);
329
+ const updateForm = (fields) => setForm((f) => ({ ...f, ...fields }));
330
+ const submitBooking = async (overrides) => {
331
+ if (!selectedSlot) return;
332
+ const validationError = validateClientForm(form);
333
+ if (validationError) {
334
+ setError(validationError);
335
+ return;
336
+ }
337
+ setLoading(true);
338
+ setError("");
339
+ try {
340
+ const svc = services.find((s) => s.id === selectedSvc);
341
+ const token = getClientToken();
342
+ const payload = buildBookingPayload({
343
+ form,
344
+ client,
345
+ selectedDate,
346
+ selectedSlot,
347
+ selectedSvc,
348
+ selectedLoc,
349
+ overrides,
350
+ svcPrice: svc?.price,
351
+ defaultNotes: opts.defaultNotes
352
+ });
353
+ const result = await createBooking(opts, payload, token || void 0);
354
+ handleSubmitResult(result);
355
+ } catch (err) {
356
+ setError(err instanceof Error ? err.message : "Error al procesar la reserva");
357
+ setLoading(false);
358
+ }
359
+ };
360
+ function handleSubmitResult(result) {
361
+ if (!result.ok) {
362
+ setError(result.message);
363
+ if (result.conflict) setSelectedSlot(null);
364
+ setLoading(false);
365
+ return;
366
+ }
367
+ opts.onBookingComplete(result.booking);
368
+ }
369
+ return {
370
+ locations,
371
+ services,
372
+ providers,
373
+ slots,
374
+ selectedLoc,
375
+ setSelectedLoc,
376
+ selectedSvc,
377
+ setSelectedSvc,
378
+ selectedProv,
379
+ setSelectedProv,
380
+ selectedDate,
381
+ setSelectedDate,
382
+ selectedSlot,
383
+ setSelectedSlot,
384
+ form,
385
+ updateForm,
386
+ loading,
387
+ error,
388
+ setError,
389
+ client,
390
+ submitBooking
391
+ };
392
+ }
393
+
394
+ // src/hooks/useClassSchedule.ts
395
+ var import_react2 = require("react");
396
+ var EMPTY_FORM2 = { firstName: "", lastName: "", email: "", phone: "", rut: "" };
397
+ function validateClassForm(form) {
398
+ if (!form.firstName.trim()) return "Por favor ingresa tu nombre.";
399
+ if (!form.lastName.trim()) return "Por favor ingresa tu apellido.";
400
+ if (!form.email.trim() || !/\S+@\S+\.\S+/.test(form.email)) return "Por favor ingresa un email v\xE1lido.";
401
+ if (!form.phone.trim()) return "Por favor ingresa tu tel\xE9fono.";
402
+ return null;
403
+ }
404
+ function buildClassBookingPayload(form, client, selectedSlot, svc, locationId) {
405
+ return {
406
+ start_time: `${selectedSlot.date}T${selectedSlot.slot.start_time}:00-04:00`,
407
+ end_time: `${selectedSlot.date}T${selectedSlot.slot.end_time}:00-04:00`,
408
+ service_id: svc?.id ?? "",
409
+ service_provider_id: selectedSlot.slot.provider_id,
410
+ location_id: locationId,
411
+ price: svc?.price || "0",
412
+ client_id: client?.id,
413
+ client_first_name: form.firstName,
414
+ client_last_name: form.lastName,
415
+ client_email: form.email,
416
+ client_phone: form.phone,
417
+ client_rut: form.rut || void 0
418
+ };
419
+ }
420
+ function resolveConflictMessage(result) {
421
+ return result.conflict ? "Ese cupo ya fue tomado por otra persona. Cierra esta ventana y elige otro horario." : result.message;
422
+ }
423
+ function useClassSchedule(opts) {
424
+ const colors = opts.colors ?? DEFAULT_PROVIDER_COLORS;
425
+ const [monday, setMonday] = (0, import_react2.useState)(() => getMondayOf(/* @__PURE__ */ new Date()));
426
+ const [slotsByDay, setSlotsByDay] = (0, import_react2.useState)({});
427
+ const [services, setServices] = (0, import_react2.useState)([]);
428
+ const [locationId, setLocationId] = (0, import_react2.useState)("");
429
+ const [loading, setLoading] = (0, import_react2.useState)(true);
430
+ const [selectedSlot, setSelectedSlot] = (0, import_react2.useState)(null);
431
+ const [providerColorMap, setProviderColorMap] = (0, import_react2.useState)({});
432
+ const [form, setForm] = (0, import_react2.useState)(EMPTY_FORM2);
433
+ const [submitting, setSubmitting] = (0, import_react2.useState)(false);
434
+ const [error, setError] = (0, import_react2.useState)("");
435
+ const [client, setClient] = (0, import_react2.useState)(null);
436
+ (0, import_react2.useEffect)(() => {
437
+ Promise.all([listLocations(opts), listServices(opts, 50)]).then(([locs, svcs]) => {
438
+ if (locs.length) setLocationId(locs[0].id);
439
+ setServices(svcs);
440
+ }).catch(() => setError("Error cargando datos"));
441
+ }, []);
442
+ (0, import_react2.useEffect)(() => {
443
+ const token = getClientToken();
444
+ if (!token) return;
445
+ fetchMe(opts.apiBase, token).then((profile) => {
446
+ if (!profile) return;
447
+ setClient(profile);
448
+ setForm((f) => ({
449
+ ...f,
450
+ firstName: profile.first_name || f.firstName,
451
+ lastName: profile.last_name || f.lastName,
452
+ email: profile.email || f.email,
453
+ phone: profile.phone || f.phone
454
+ }));
455
+ });
456
+ }, []);
457
+ const loadWeek = (0, import_react2.useCallback)(async (mon, locId) => {
458
+ if (!locId) return;
459
+ setLoading(true);
460
+ const days = Array.from({ length: 7 }, (_, i) => {
461
+ const d = new Date(mon);
462
+ d.setDate(mon.getDate() + i);
463
+ return d;
464
+ });
465
+ const results = await Promise.all(
466
+ days.map(async (d) => {
467
+ const ds = dateStr(d);
468
+ try {
469
+ const slots = await getAvailableSlots(opts, { locationId: locId, startDate: ds });
470
+ return { date: ds, slots };
471
+ } catch {
472
+ return { date: ds, slots: [] };
473
+ }
474
+ })
475
+ );
476
+ const map = {};
477
+ const colorMap = {};
478
+ let colorIdx = 0;
479
+ results.forEach(({ date, slots }) => {
480
+ map[date] = slots;
481
+ slots.forEach((s) => {
482
+ if (!colorMap[s.provider_id]) {
483
+ colorMap[s.provider_id] = colors[colorIdx % colors.length];
484
+ colorIdx++;
485
+ }
486
+ });
487
+ });
488
+ setSlotsByDay(map);
489
+ setProviderColorMap((prev) => ({ ...prev, ...colorMap }));
490
+ setLoading(false);
491
+ }, []);
492
+ (0, import_react2.useEffect)(() => {
493
+ if (locationId) loadWeek(monday, locationId);
494
+ }, [monday, locationId, loadWeek]);
495
+ const updateForm = (fields) => setForm((f) => ({ ...f, ...fields }));
496
+ const submitBooking = async (e) => {
497
+ e.preventDefault();
498
+ if (!selectedSlot) return;
499
+ const validationError = validateClassForm(form);
500
+ if (validationError) {
501
+ setError(validationError);
502
+ return;
503
+ }
504
+ setSubmitting(true);
505
+ setError("");
506
+ try {
507
+ const svc = services.find((s) => s.name === selectedSlot.slot.service_name);
508
+ const token = getClientToken();
509
+ const payload = buildClassBookingPayload(form, client, selectedSlot, svc, locationId);
510
+ const result = await createBooking(opts, payload, token || void 0);
511
+ handleSubmitResult(result);
512
+ } catch (err) {
513
+ setError(err instanceof Error ? err.message : "Error al procesar");
514
+ setSubmitting(false);
515
+ }
516
+ };
517
+ function handleSubmitResult(result) {
518
+ if (!result.ok) {
519
+ setError(resolveConflictMessage(result));
520
+ setSubmitting(false);
521
+ return;
522
+ }
523
+ opts.onBookingComplete();
524
+ }
525
+ return {
526
+ monday,
527
+ setMonday,
528
+ slotsByDay,
529
+ loading,
530
+ providerColorMap,
531
+ selectedSlot,
532
+ setSelectedSlot,
533
+ form,
534
+ updateForm,
535
+ submitting,
536
+ error,
537
+ setError,
538
+ client,
539
+ submitBooking
540
+ };
541
+ }
542
+
543
+ // src/hooks/useMagicLinkAuth.ts
544
+ var import_react3 = require("react");
545
+ function useMagicLinkAuth(opts) {
546
+ const [email, setEmail] = (0, import_react3.useState)("");
547
+ const [firstName, setFirstName] = (0, import_react3.useState)("");
548
+ const [lastName, setLastName] = (0, import_react3.useState)("");
549
+ const [phone, setPhone] = (0, import_react3.useState)("");
550
+ const [rut, setRut] = (0, import_react3.useState)("");
551
+ const [step, setStep] = (0, import_react3.useState)("email");
552
+ const [error, setError] = (0, import_react3.useState)("");
553
+ const [loading, setLoading] = (0, import_react3.useState)(false);
554
+ const handleMagicLink = async (e) => {
555
+ e.preventDefault();
556
+ if (!email.trim() || !/\S+@\S+\.\S+/.test(email)) {
557
+ setError("Ingresa un email v\xE1lido.");
558
+ return;
559
+ }
560
+ setLoading(true);
561
+ setError("");
562
+ try {
563
+ await sendMagicLink(opts, email, opts.frontendUrl, {
564
+ firstName: firstName || void 0,
565
+ lastName: lastName || void 0,
566
+ phone: phone || void 0,
567
+ rut: rut || void 0
568
+ });
569
+ setStep("sent");
570
+ } catch (err) {
571
+ setError(err instanceof Error ? err.message : "Error enviando el link.");
572
+ } finally {
573
+ setLoading(false);
574
+ }
575
+ };
576
+ const handleGoogle = () => {
577
+ if (!opts.googleClientId || typeof window === "undefined" || !window.google) return;
578
+ window.google.accounts.id.initialize({
579
+ client_id: opts.googleClientId,
580
+ callback: async (response) => {
581
+ setLoading(true);
582
+ try {
583
+ const data = await loginWithGoogle(opts, response.credential);
584
+ setClientToken(data.access_token);
585
+ opts.onGoogleLoggedIn();
586
+ } catch (err) {
587
+ setError(err instanceof Error ? err.message : "Error con Google.");
588
+ setLoading(false);
589
+ }
590
+ }
591
+ });
592
+ window.google.accounts.id.prompt();
593
+ };
594
+ const reset = () => {
595
+ setStep("email");
596
+ setError("");
597
+ };
598
+ return {
599
+ email,
600
+ setEmail,
601
+ firstName,
602
+ setFirstName,
603
+ lastName,
604
+ setLastName,
605
+ phone,
606
+ setPhone,
607
+ rut,
608
+ setRut,
609
+ step,
610
+ error,
611
+ loading,
612
+ handleMagicLink,
613
+ handleGoogle,
614
+ reset
615
+ };
616
+ }
617
+
618
+ // src/hooks/useRegisterForm.ts
619
+ var import_react4 = require("react");
620
+ var EMPTY = { firstName: "", lastName: "", email: "", phone: "", password: "", confirm: "" };
621
+ function useRegisterForm(opts) {
622
+ const [form, setForm] = (0, import_react4.useState)(EMPTY);
623
+ const [error, setError] = (0, import_react4.useState)("");
624
+ const [loading, setLoading] = (0, import_react4.useState)(false);
625
+ const updateField = (fields) => setForm((f) => ({ ...f, ...fields }));
626
+ const handleSubmit = async (e) => {
627
+ e.preventDefault();
628
+ if (!form.firstName.trim()) {
629
+ setError("Ingresa tu nombre.");
630
+ return;
631
+ }
632
+ if (!form.lastName.trim()) {
633
+ setError("Ingresa tu apellido.");
634
+ return;
635
+ }
636
+ if (!form.email.trim()) {
637
+ setError("Ingresa tu email.");
638
+ return;
639
+ }
640
+ if (form.password.length < 6) {
641
+ setError("La contrase\xF1a debe tener al menos 6 caracteres.");
642
+ return;
643
+ }
644
+ if (form.password !== form.confirm) {
645
+ setError("Las contrase\xF1as no coinciden.");
646
+ return;
647
+ }
648
+ setLoading(true);
649
+ setError("");
650
+ try {
651
+ const data = await register(opts, form);
652
+ setClientToken(data.access_token);
653
+ opts.onRegistered();
654
+ } catch (err) {
655
+ setError(err instanceof Error ? err.message : "Error al registrarse.");
656
+ } finally {
657
+ setLoading(false);
658
+ }
659
+ };
660
+ return { form, updateField, error, loading, handleSubmit };
661
+ }
662
+
663
+ // src/hooks/useClientAccount.ts
664
+ var import_react5 = require("react");
665
+ function useClientAccount(opts) {
666
+ const [profile, setProfile] = (0, import_react5.useState)(null);
667
+ const [upcoming, setUpcoming] = (0, import_react5.useState)([]);
668
+ const [history, setHistory] = (0, import_react5.useState)([]);
669
+ const [packages, setPackages] = (0, import_react5.useState)([]);
670
+ const [loading, setLoading] = (0, import_react5.useState)(true);
671
+ (0, import_react5.useEffect)(() => {
672
+ const token = getClientToken();
673
+ if (!token) {
674
+ opts.onUnauthenticated();
675
+ return;
676
+ }
677
+ Promise.all([
678
+ getMe(opts.apiBase, token),
679
+ getMyBookings(opts.apiBase, token, { upcoming: true, perPage: 10 }),
680
+ getMyBookings(opts.apiBase, token, { perPage: 20 }),
681
+ getMyPackages(opts.apiBase, token)
682
+ ]).then(([prof, up, hist, pkgs]) => {
683
+ setProfile(prof);
684
+ setUpcoming(up);
685
+ setHistory(hist);
686
+ setPackages(pkgs);
687
+ setLoading(false);
688
+ }).catch(() => opts.onUnauthenticated());
689
+ }, []);
690
+ const handleLogout = () => {
691
+ clearClientToken();
692
+ opts.onLoggedOut();
693
+ };
694
+ const totalCredits = packages.reduce((s, p) => s + p.remaining_credits, 0);
695
+ return { profile, upcoming, history, packages, loading, totalCredits, handleLogout };
696
+ }
697
+ // Annotate the CommonJS export names for ESM import in node:
698
+ 0 && (module.exports = {
699
+ DAY_NAMES,
700
+ DAY_NAMES_FULL,
701
+ DEFAULT_PROVIDER_COLORS,
702
+ clearClientToken,
703
+ createBooking,
704
+ createClient,
705
+ dateStr,
706
+ fetchMe,
707
+ formatMonthRange,
708
+ getAvailableSlots,
709
+ getClientToken,
710
+ getMe,
711
+ getMondayOf,
712
+ getMyBookings,
713
+ getMyPackages,
714
+ listLocations,
715
+ listProviders,
716
+ listServices,
717
+ loginWithGoogle,
718
+ register,
719
+ sendMagicLink,
720
+ setClientToken,
721
+ useBookingFlow,
722
+ useClassSchedule,
723
+ useClientAccount,
724
+ useMagicLinkAuth,
725
+ useRegisterForm,
726
+ verifyMagicLink
727
+ });
728
+ //# sourceMappingURL=index.cjs.map