@aranova/tracking-react 1.2.5 → 1.4.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/README.md +77 -0
- package/dist/{tracking-config-runtime-C6H7ofmo.d.ts → bookings-CqCuuyP6.d.mts} +165 -1
- package/dist/{tracking-config-runtime-C6H7ofmo.d.mts → bookings-DIu1ho6v.d.ts} +165 -1
- package/dist/client.d.mts +58 -5
- package/dist/client.d.ts +58 -5
- package/dist/client.js +287 -11
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +282 -11
- package/dist/client.mjs.map +1 -1
- package/dist/form-submit-BLb28aCk.d.mts +75 -0
- package/dist/form-submit-BLb28aCk.d.ts +75 -0
- package/dist/index.d.mts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +470 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +465 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{ingest-typed-CUpdCtmM.d.ts → ingest-typed-D2S3Kb8m.d.ts} +83 -2
- package/dist/{ingest-typed-oAwnKqH2.d.mts → ingest-typed-D7UZr0EM.d.mts} +83 -2
- package/dist/{phone-utils-qLfVpQI7.d.mts → phone-utils-CM8eUPz8.d.mts} +2 -74
- package/dist/{phone-utils-qLfVpQI7.d.ts → phone-utils-l8ey3Zdm.d.ts} +2 -74
- package/dist/phone.d.mts +2 -1
- package/dist/phone.d.ts +2 -1
- package/dist/{sales-C3O_xxZ_.d.ts → sales-CxJVthx_.d.mts} +23 -2
- package/dist/{sales-BuKD_bdo.d.mts → sales-hlcVTJSS.d.ts} +23 -2
- package/dist/sales.d.mts +3 -2
- package/dist/sales.d.ts +3 -2
- package/dist/sales.js +386 -38
- package/dist/sales.js.map +1 -1
- package/dist/sales.mjs +384 -38
- package/dist/sales.mjs.map +1 -1
- package/package.json +1 -1
package/dist/sales.js
CHANGED
|
@@ -32,6 +32,7 @@ var sales_exports = {};
|
|
|
32
32
|
__export(sales_exports, {
|
|
33
33
|
AranovaApiError: () => AranovaApiError,
|
|
34
34
|
NAMED_RANGES: () => NAMED_RANGES,
|
|
35
|
+
SALE_SOURCES: () => SALE_SOURCES,
|
|
35
36
|
SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES,
|
|
36
37
|
TRACKING_RANGES: () => TRACKING_RANGES,
|
|
37
38
|
createSalesClient: () => createSalesClient,
|
|
@@ -40,6 +41,7 @@ __export(sales_exports, {
|
|
|
40
41
|
formatDateInTz: () => formatDateInTz,
|
|
41
42
|
formatMoney: () => formatMoney,
|
|
42
43
|
fromMinor: () => fromMinor,
|
|
44
|
+
newBookingAttemptKey: () => newBookingAttemptKey,
|
|
43
45
|
saleCreateSchema: () => saleCreateSchema,
|
|
44
46
|
saleItemSchema: () => saleItemSchema,
|
|
45
47
|
saleServiceSchema: () => saleServiceSchema,
|
|
@@ -221,12 +223,106 @@ function fireConversionWithConsent(input, options) {
|
|
|
221
223
|
}
|
|
222
224
|
}
|
|
223
225
|
|
|
224
|
-
//
|
|
225
|
-
var
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
226
|
+
// ../../contracts/autoleap-booking-links.json
|
|
227
|
+
var autoleap_booking_links_default = {
|
|
228
|
+
hosts: ["myalp.io"]
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// ../tracking-core/src/integrations-public.ts
|
|
232
|
+
var HANDOFF_HOSTS = new Set(autoleap_booking_links_default.hosts);
|
|
233
|
+
function getBookingIntegration(integrations) {
|
|
234
|
+
const booking = integrations?.autoleap?.booking;
|
|
235
|
+
if (!booking) return null;
|
|
236
|
+
const rules = booking.rules ? camelRules(booking.rules) : null;
|
|
237
|
+
return {
|
|
238
|
+
provider: "autoleap",
|
|
239
|
+
enabled: booking.enabled,
|
|
240
|
+
fallbackUrl: booking.fallback_url,
|
|
241
|
+
acceptsBookings: booking.accepts_bookings && rules !== null && booking.config_version !== null,
|
|
242
|
+
timezone: booking.timezone,
|
|
243
|
+
configVersion: booking.config_version,
|
|
244
|
+
rules,
|
|
245
|
+
options: booking.options.map((option) => ({ key: option.key, label: option.label })),
|
|
246
|
+
mode: booking.mode ?? "live"
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function camelRules(rules) {
|
|
250
|
+
return {
|
|
251
|
+
minDaysAhead: rules.min_days_ahead,
|
|
252
|
+
maxDaysAhead: rules.max_days_ahead,
|
|
253
|
+
hours: rules.hours,
|
|
254
|
+
closedDates: rules.closed_dates,
|
|
255
|
+
specialHours: rules.special_hours,
|
|
256
|
+
vehicleRequired: rules.vehicle_required,
|
|
257
|
+
dropOffOptions: rules.drop_off_options
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function buildBookingHandoffUrl(fallbackUrl, prefill = {}) {
|
|
261
|
+
let url;
|
|
262
|
+
try {
|
|
263
|
+
url = new URL(fallbackUrl);
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (url.protocol !== "https:" || !HANDOFF_HOSTS.has(url.hostname) || url.username || url.password || url.port) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const [firstName, lastName] = prefill.firstName?.trim() ? [prefill.firstName, prefill.lastName] : splitFullName(prefill.name);
|
|
271
|
+
const values = [
|
|
272
|
+
["firstName", firstName?.trim()],
|
|
273
|
+
["lastName", lastName?.trim()],
|
|
274
|
+
["phone", prefill.phone ? nationalDigits(prefill.phone) : ""],
|
|
275
|
+
["email", prefill.email?.trim()]
|
|
276
|
+
];
|
|
277
|
+
for (const [key, value] of values) {
|
|
278
|
+
if (value) url.searchParams.set(key, value);
|
|
279
|
+
}
|
|
280
|
+
return url.toString();
|
|
281
|
+
}
|
|
282
|
+
function splitFullName(name) {
|
|
283
|
+
const [first = "", ...rest] = (name ?? "").trim().split(/\s+/);
|
|
284
|
+
return [first, rest.join(" ")];
|
|
285
|
+
}
|
|
286
|
+
function nationalDigits(phone) {
|
|
287
|
+
const parsed = parsePhone(phone);
|
|
288
|
+
return (parsed.isValid ? parsed.national : phone).replace(/\D/g, "");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ../tracking-core/src/uuid.ts
|
|
292
|
+
function uuidV4() {
|
|
293
|
+
const cryptoApi = globalThis.crypto;
|
|
294
|
+
if (typeof cryptoApi?.randomUUID === "function") return cryptoApi.randomUUID();
|
|
295
|
+
const bytes = new Uint8Array(16);
|
|
296
|
+
if (typeof cryptoApi?.getRandomValues === "function") cryptoApi.getRandomValues(bytes);
|
|
297
|
+
else for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
298
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
299
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
300
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
301
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ../tracking-core/src/session.ts
|
|
305
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
306
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
307
|
+
function readLocalStorage(key) {
|
|
308
|
+
try {
|
|
309
|
+
return window.localStorage.getItem(key);
|
|
310
|
+
} catch {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function peekSessionId(now = Date.now()) {
|
|
315
|
+
if (typeof window === "undefined") return null;
|
|
316
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
317
|
+
if (!raw) return null;
|
|
318
|
+
try {
|
|
319
|
+
const parsed = JSON.parse(raw);
|
|
320
|
+
if (typeof parsed.id !== "string" || typeof parsed.last_event_at !== "number") return null;
|
|
321
|
+
return now - parsed.last_event_at <= SESSION_IDLE_MS ? parsed.id : null;
|
|
322
|
+
} catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
230
326
|
|
|
231
327
|
// ../tracking-core/src/resources/http/errors.ts
|
|
232
328
|
var AranovaApiError = class extends Error {
|
|
@@ -236,9 +332,66 @@ var AranovaApiError = class extends Error {
|
|
|
236
332
|
this.status = options.status;
|
|
237
333
|
this.code = options.code;
|
|
238
334
|
this.requestId = options.requestId;
|
|
335
|
+
this.context = options.context;
|
|
239
336
|
}
|
|
240
337
|
};
|
|
241
338
|
|
|
339
|
+
// ../tracking-core/src/resources/intl/date.ts
|
|
340
|
+
function formatDateInTz(iso3, timeZone, opts, locale) {
|
|
341
|
+
const date = new Date(iso3);
|
|
342
|
+
if (Number.isNaN(date.getTime())) return iso3;
|
|
343
|
+
return new Intl.DateTimeFormat(locale, {
|
|
344
|
+
year: "numeric",
|
|
345
|
+
month: "short",
|
|
346
|
+
day: "2-digit",
|
|
347
|
+
hour: "2-digit",
|
|
348
|
+
minute: "2-digit",
|
|
349
|
+
...opts,
|
|
350
|
+
timeZone
|
|
351
|
+
}).format(date);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ../tracking-core/src/resources/sales/money.ts
|
|
355
|
+
var MINOR_UNIT_EXPONENT = {
|
|
356
|
+
USD: 2,
|
|
357
|
+
CAD: 2
|
|
358
|
+
};
|
|
359
|
+
function exponentFor(currency) {
|
|
360
|
+
return MINOR_UNIT_EXPONENT[currency] ?? 2;
|
|
361
|
+
}
|
|
362
|
+
function toMinor(amount, currency) {
|
|
363
|
+
return Math.round(amount * 10 ** exponentFor(currency));
|
|
364
|
+
}
|
|
365
|
+
function fromMinor(cents, currency) {
|
|
366
|
+
return cents / 10 ** exponentFor(currency);
|
|
367
|
+
}
|
|
368
|
+
function formatMoney(cents, currency, locale) {
|
|
369
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(
|
|
370
|
+
fromMinor(cents, currency)
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ../tracking-core/src/resources/tracking-config-runtime.ts
|
|
375
|
+
var DEFAULT_CDN_BASE_URL = "https://demos.aranova.io";
|
|
376
|
+
function trackingConfigKey(ref) {
|
|
377
|
+
if (ref.siteId) return `tracking-config/v1/sites/${ref.siteId}-${ref.environment}.json`;
|
|
378
|
+
return `tracking-config/v1/${ref.businessId}-${ref.environment}.json`;
|
|
379
|
+
}
|
|
380
|
+
function resolveTrackingConfigUrl(ref) {
|
|
381
|
+
if (ref.cdnUrl) return ref.cdnUrl;
|
|
382
|
+
const configured = ref.cdnBaseUrl?.trim();
|
|
383
|
+
const base = (configured || DEFAULT_CDN_BASE_URL).replace(/\/+$/, "");
|
|
384
|
+
return `${base}/${trackingConfigKey(ref)}`;
|
|
385
|
+
}
|
|
386
|
+
var RETRY_MAX_MS = 5 * 6e4;
|
|
387
|
+
|
|
388
|
+
// ../tracking-core/src/ingest.ts
|
|
389
|
+
var API_KEY_HEADER = "X-Aranova-Api-Key";
|
|
390
|
+
var SDK_VERSION_HEADER = "X-Aranova-Sdk-Version";
|
|
391
|
+
var SDK_PACKAGE_HEADER = "X-Aranova-Sdk-Package";
|
|
392
|
+
var SDK_SURFACE_HEADER = "X-Aranova-Sdk-Surface";
|
|
393
|
+
var SDK_ENVIRONMENT_HEADER = "X-Aranova-Sdk-Environment";
|
|
394
|
+
|
|
242
395
|
// ../tracking-core/src/resources/http/request.ts
|
|
243
396
|
function identityHeaders(config) {
|
|
244
397
|
const headers = { [API_KEY_HEADER]: config.apiKey };
|
|
@@ -262,19 +415,24 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
262
415
|
if (!response.ok) {
|
|
263
416
|
let detail;
|
|
264
417
|
let code;
|
|
418
|
+
let context;
|
|
265
419
|
try {
|
|
266
420
|
const parsed = await response.json();
|
|
267
421
|
if (parsed && typeof parsed === "object") {
|
|
268
422
|
const record3 = parsed;
|
|
269
423
|
if (typeof record3.detail === "string") detail = record3.detail;
|
|
270
424
|
if (typeof record3.code === "string") code = record3.code;
|
|
425
|
+
if (record3.context && typeof record3.context === "object") {
|
|
426
|
+
context = record3.context;
|
|
427
|
+
}
|
|
271
428
|
}
|
|
272
429
|
} catch {
|
|
273
430
|
}
|
|
274
431
|
throw new AranovaApiError(detail ?? response.statusText ?? "Request failed", {
|
|
275
432
|
status: response.status,
|
|
276
433
|
code,
|
|
277
|
-
requestId: response.headers.get("x-request-id") ?? void 0
|
|
434
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
435
|
+
context
|
|
278
436
|
});
|
|
279
437
|
}
|
|
280
438
|
if (response.status === 204) return void 0;
|
|
@@ -284,39 +442,212 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
284
442
|
// ../tracking-core/src/resources/sales/transport.ts
|
|
285
443
|
var salesRequest = apiRequest;
|
|
286
444
|
|
|
287
|
-
// ../tracking-core/src/resources/
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
month: "short",
|
|
294
|
-
day: "2-digit",
|
|
295
|
-
hour: "2-digit",
|
|
296
|
-
minute: "2-digit",
|
|
297
|
-
...opts,
|
|
298
|
-
timeZone
|
|
299
|
-
}).format(date);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// ../tracking-core/src/resources/sales/money.ts
|
|
303
|
-
var MINOR_UNIT_EXPONENT = {
|
|
304
|
-
USD: 2,
|
|
305
|
-
CAD: 2
|
|
445
|
+
// ../tracking-core/src/resources/sales/bookings.ts
|
|
446
|
+
var BOOKING_SIMULATE_HEADER = "X-Aranova-Simulate";
|
|
447
|
+
var BOOKING_ERROR_CODES = {
|
|
448
|
+
unavailable: "booking_unavailable",
|
|
449
|
+
stale: "booking_config_stale",
|
|
450
|
+
rejected: "booking_rejected"
|
|
306
451
|
};
|
|
307
|
-
|
|
308
|
-
|
|
452
|
+
var POLL_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
453
|
+
var INVALID_DETAILS = "Please check your details and try again.";
|
|
454
|
+
var newBookingAttemptKey = uuidV4;
|
|
455
|
+
function sleep(ms) {
|
|
456
|
+
return new Promise((resolve) => {
|
|
457
|
+
setTimeout(resolve, ms);
|
|
458
|
+
});
|
|
309
459
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
460
|
+
function outcomeOf(body) {
|
|
461
|
+
const mode = body.mode ?? "live";
|
|
462
|
+
if (body.status === "created")
|
|
463
|
+
return { status: "created", id: body.id, requestCode: body.request_code, mode };
|
|
464
|
+
if (body.status === "pending") return { status: "pending", id: body.id, mode };
|
|
465
|
+
return { status: "failed", id: body.id, fallbackUrl: body.fallback_url, mode };
|
|
312
466
|
}
|
|
313
|
-
function
|
|
314
|
-
|
|
467
|
+
function formSubmitPayload(input, formId, labels) {
|
|
468
|
+
const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
|
|
469
|
+
const field = (name, type, label, value) => ({
|
|
470
|
+
name,
|
|
471
|
+
type,
|
|
472
|
+
label,
|
|
473
|
+
value
|
|
474
|
+
});
|
|
475
|
+
return {
|
|
476
|
+
form: {
|
|
477
|
+
id: formId,
|
|
478
|
+
action: "/tracking/bookings",
|
|
479
|
+
fields: [
|
|
480
|
+
field("first_name", "text", "First name", input.firstName),
|
|
481
|
+
field("last_name", "text", "Last name", input.lastName),
|
|
482
|
+
// E.164 on the wire: it keys the customer and enhanced-conversion matching.
|
|
483
|
+
field("phone", "tel", "Phone", toE164(input.phone) ?? input.phone),
|
|
484
|
+
field("email", "email", "Email", input.email ?? ""),
|
|
485
|
+
field(
|
|
486
|
+
"service",
|
|
487
|
+
"select",
|
|
488
|
+
"Service",
|
|
489
|
+
input.services.map((key) => labels.get(key) ?? key).join(", ")
|
|
490
|
+
),
|
|
491
|
+
field("vehicle", "text", "Vehicle", vehicle),
|
|
492
|
+
field(
|
|
493
|
+
"start",
|
|
494
|
+
"text",
|
|
495
|
+
"Requested time",
|
|
496
|
+
`${input.startDate} ${String(input.startHour).padStart(2, "0")}:00`
|
|
497
|
+
),
|
|
498
|
+
field("note", "textarea", "Notes", input.notes ?? "")
|
|
499
|
+
]
|
|
500
|
+
},
|
|
501
|
+
page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
|
|
502
|
+
};
|
|
315
503
|
}
|
|
316
|
-
function
|
|
317
|
-
return
|
|
318
|
-
|
|
319
|
-
|
|
504
|
+
function runtimeOf(firing) {
|
|
505
|
+
return firing && "integrations" in firing ? firing : null;
|
|
506
|
+
}
|
|
507
|
+
function isLocalUrl(url) {
|
|
508
|
+
try {
|
|
509
|
+
return ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname);
|
|
510
|
+
} catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function bookingSetupMismatch(endpoint, runtime) {
|
|
515
|
+
if (!runtime || !isLocalUrl(endpoint)) return null;
|
|
516
|
+
const configUrl = resolveTrackingConfigUrl(runtime.ref);
|
|
517
|
+
if (isLocalUrl(configUrl)) return null;
|
|
518
|
+
return `[aranova] Bookings go to the local API (${endpoint}) but the runtime config comes from ${configUrl}. Set NEXT_PUBLIC_ARANOVA_CDN_BASE_URL to the local bucket (http://localhost:9100/aranova-demos) so both come from one backend.`;
|
|
519
|
+
}
|
|
520
|
+
function createSalesBookingsClient(config, firing) {
|
|
521
|
+
const submittedForms = /* @__PURE__ */ new Set();
|
|
522
|
+
const staleAttempts = /* @__PURE__ */ new Set();
|
|
523
|
+
let isSetupChecked = false;
|
|
524
|
+
function warnOnSetupMismatch(runtime) {
|
|
525
|
+
if (isSetupChecked) return;
|
|
526
|
+
isSetupChecked = true;
|
|
527
|
+
const warning = bookingSetupMismatch(config.endpoint, runtime);
|
|
528
|
+
if (warning) console.warn(warning);
|
|
529
|
+
}
|
|
530
|
+
function handoff(input) {
|
|
531
|
+
const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
|
|
532
|
+
if (!booking) return null;
|
|
533
|
+
return buildBookingHandoffUrl(booking.fallbackUrl, {
|
|
534
|
+
firstName: input.firstName,
|
|
535
|
+
lastName: input.lastName,
|
|
536
|
+
phone: input.phone,
|
|
537
|
+
email: input.email
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
function failedBeforeSend(input) {
|
|
541
|
+
const mode = getBookingIntegration(runtimeOf(firing)?.integrations())?.mode ?? "live";
|
|
542
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input), mode };
|
|
543
|
+
}
|
|
544
|
+
function withHandoff(outcome, input) {
|
|
545
|
+
if (outcome.status === "failed" && !outcome.fallbackUrl) {
|
|
546
|
+
return { ...outcome, fallbackUrl: handoff(input) };
|
|
547
|
+
}
|
|
548
|
+
return outcome;
|
|
549
|
+
}
|
|
550
|
+
async function poll(id, mode) {
|
|
551
|
+
let outcome = { status: "pending", id, mode };
|
|
552
|
+
for (const delay of POLL_DELAYS_MS) {
|
|
553
|
+
await sleep(delay);
|
|
554
|
+
try {
|
|
555
|
+
const path = `/bookings/${encodeURIComponent(id)}`;
|
|
556
|
+
outcome = outcomeOf(await salesRequest(config, "GET", path));
|
|
557
|
+
} catch {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (outcome.status !== "pending") return outcome;
|
|
561
|
+
}
|
|
562
|
+
return outcome;
|
|
563
|
+
}
|
|
564
|
+
async function rejected(error, input, attemptKey, runtime) {
|
|
565
|
+
if (!(error instanceof AranovaApiError)) {
|
|
566
|
+
return failedBeforeSend(input);
|
|
567
|
+
}
|
|
568
|
+
if (error.code === BOOKING_ERROR_CODES.stale) {
|
|
569
|
+
if (staleAttempts.has(attemptKey)) return failedBeforeSend(input);
|
|
570
|
+
staleAttempts.add(attemptKey);
|
|
571
|
+
await runtime?.revalidate();
|
|
572
|
+
return { status: "stale", message: error.message };
|
|
573
|
+
}
|
|
574
|
+
if (error.code === BOOKING_ERROR_CODES.rejected) {
|
|
575
|
+
const reasons = error.context?.reasons;
|
|
576
|
+
return {
|
|
577
|
+
status: "invalid",
|
|
578
|
+
message: error.message,
|
|
579
|
+
reasons: Array.isArray(reasons) ? reasons.map(String) : []
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
if (error.code === "validation_error") {
|
|
583
|
+
return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
|
|
584
|
+
}
|
|
585
|
+
return failedBeforeSend(input);
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
async submit(input, { attemptKey, formSubmit, simulate }) {
|
|
589
|
+
registerCapability("booking_integration");
|
|
590
|
+
const runtime = runtimeOf(firing);
|
|
591
|
+
warnOnSetupMismatch(runtime);
|
|
592
|
+
const booking = getBookingIntegration(runtime?.integrations());
|
|
593
|
+
if (!booking?.acceptsBookings || booking.configVersion === null) {
|
|
594
|
+
return failedBeforeSend(input);
|
|
595
|
+
}
|
|
596
|
+
if (formSubmit && !submittedForms.has(attemptKey)) {
|
|
597
|
+
submittedForms.add(attemptKey);
|
|
598
|
+
const labels = new Map(booking.options.map((option) => [option.key, option.label]));
|
|
599
|
+
formSubmit.tracking.trackEvent(
|
|
600
|
+
"form_submit",
|
|
601
|
+
formSubmitPayload(input, formSubmit.formId, labels)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const body = {
|
|
605
|
+
idempotency_key: attemptKey,
|
|
606
|
+
// The tracking client's own visit, so the booking and its form_submit share one session.
|
|
607
|
+
session_id: formSubmit?.tracking.getSessionId() ?? peekSessionId() ?? `no-session-${attemptKey}`,
|
|
608
|
+
config_version: booking.configVersion,
|
|
609
|
+
start_date: input.startDate,
|
|
610
|
+
start_hour: input.startHour,
|
|
611
|
+
first_name: input.firstName.trim(),
|
|
612
|
+
last_name: input.lastName.trim(),
|
|
613
|
+
phone: input.phone,
|
|
614
|
+
email: input.email?.trim() || null,
|
|
615
|
+
vehicle: input.vehicle ?? null,
|
|
616
|
+
drop_off: input.dropOff ?? null,
|
|
617
|
+
services: input.services,
|
|
618
|
+
notes: input.notes?.trim() || null,
|
|
619
|
+
marketing_sms_consent: input.marketingSmsConsent === true,
|
|
620
|
+
page_path: typeof window === "undefined" ? null : window.location.pathname
|
|
621
|
+
};
|
|
622
|
+
let response;
|
|
623
|
+
try {
|
|
624
|
+
const headers = simulate ? { [BOOKING_SIMULATE_HEADER]: simulate } : void 0;
|
|
625
|
+
response = await salesRequest(
|
|
626
|
+
config,
|
|
627
|
+
"POST",
|
|
628
|
+
"/bookings",
|
|
629
|
+
body,
|
|
630
|
+
headers
|
|
631
|
+
);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
return rejected(error, input, attemptKey, runtime);
|
|
634
|
+
}
|
|
635
|
+
const outcome = outcomeOf(response);
|
|
636
|
+
return withHandoff(
|
|
637
|
+
outcome.status === "pending" ? await poll(outcome.id, outcome.mode) : outcome,
|
|
638
|
+
input
|
|
639
|
+
);
|
|
640
|
+
},
|
|
641
|
+
async status(id) {
|
|
642
|
+
return outcomeOf(
|
|
643
|
+
await salesRequest(
|
|
644
|
+
config,
|
|
645
|
+
"GET",
|
|
646
|
+
`/bookings/${encodeURIComponent(id)}`
|
|
647
|
+
)
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
};
|
|
320
651
|
}
|
|
321
652
|
|
|
322
653
|
// ../tracking-core/src/resources/sales/client.ts
|
|
@@ -493,7 +824,8 @@ function createSalesClient(config) {
|
|
|
493
824
|
async config() {
|
|
494
825
|
return salesRequest(config, "GET", "/business/config");
|
|
495
826
|
}
|
|
496
|
-
}
|
|
827
|
+
},
|
|
828
|
+
bookings: createSalesBookingsClient(config, config.firing)
|
|
497
829
|
};
|
|
498
830
|
}
|
|
499
831
|
|
|
@@ -502,6 +834,12 @@ var z2 = __toESM(require("zod/mini"));
|
|
|
502
834
|
|
|
503
835
|
// ../tracking-core/src/resources/sales/generated/zod.gen.ts
|
|
504
836
|
var z = __toESM(require("zod/mini"));
|
|
837
|
+
var zAutoLeapSaleFacts = z.object({
|
|
838
|
+
first_order: z.boolean(),
|
|
839
|
+
fleet_customer: z.boolean(),
|
|
840
|
+
invoice_code: z.string(),
|
|
841
|
+
invoice_id: z.string()
|
|
842
|
+
});
|
|
505
843
|
var zErrorCode = z.enum([
|
|
506
844
|
"missing_credentials",
|
|
507
845
|
"invalid_api_key",
|
|
@@ -529,6 +867,9 @@ var zErrorCode = z.enum([
|
|
|
529
867
|
"booking_not_reschedulable",
|
|
530
868
|
"manage_token_invalid",
|
|
531
869
|
"google_error",
|
|
870
|
+
"booking_unavailable",
|
|
871
|
+
"booking_config_stale",
|
|
872
|
+
"booking_rejected",
|
|
532
873
|
"calendar_not_owned",
|
|
533
874
|
"calendar_is_sandbox",
|
|
534
875
|
"sandbox_exists",
|
|
@@ -570,6 +911,7 @@ var zSaleServiceInputSchema = z.strictObject({
|
|
|
570
911
|
amount_cents: z.int().check(z.gte(0)),
|
|
571
912
|
service: z.string()
|
|
572
913
|
});
|
|
914
|
+
var zSaleSource = z.enum(["api", "admin", "autoleap"]);
|
|
573
915
|
var zSmsAutomationState = z.enum([
|
|
574
916
|
"needs_check",
|
|
575
917
|
"failed",
|
|
@@ -601,7 +943,8 @@ var zSmsWithheldReason = z.enum([
|
|
|
601
943
|
"number_not_active",
|
|
602
944
|
"a2p_registration_required",
|
|
603
945
|
"alert_recipient_cannot_receive_sms",
|
|
604
|
-
"line_type_unresolved"
|
|
946
|
+
"line_type_unresolved",
|
|
947
|
+
"sale_source_sends_own_messages"
|
|
605
948
|
]);
|
|
606
949
|
var zSaleSmsStatusSchema = z.object({
|
|
607
950
|
can_send_now: z._default(z.boolean(), false),
|
|
@@ -710,6 +1053,8 @@ var zSaleEntitySchema = z.object({
|
|
|
710
1053
|
sms_automation: z.nullable(zSaleSmsStatusSchema),
|
|
711
1054
|
sms_consent: z._default(z.boolean(), false),
|
|
712
1055
|
sms_consent_granted_at: z.nullable(z.iso.datetime({ offset: true })),
|
|
1056
|
+
source: zSaleSource,
|
|
1057
|
+
source_facts: z.nullable(zAutoLeapSaleFacts),
|
|
713
1058
|
updated_at: z.iso.datetime({ offset: true }),
|
|
714
1059
|
writeback: z.nullable(zSaleWritebackStatusSchema)
|
|
715
1060
|
});
|
|
@@ -719,6 +1064,7 @@ var saleItemSchema = zSaleItemInputSchema;
|
|
|
719
1064
|
var saleServiceSchema = zSaleServiceInputSchema;
|
|
720
1065
|
var currencySchema = zSupportedCurrency;
|
|
721
1066
|
var SUPPORTED_CURRENCIES = currencySchema.options;
|
|
1067
|
+
var SALE_SOURCES = zSaleSource.options;
|
|
722
1068
|
var centsSchema = z2.int().check(z2.gte(0));
|
|
723
1069
|
var metadataSchema = z2.record(z2.string(), z2.unknown());
|
|
724
1070
|
var customerNameSchema = z2.string().check(z2.maxLength(200));
|
|
@@ -799,6 +1145,7 @@ async function fetchServices(config) {
|
|
|
799
1145
|
0 && (module.exports = {
|
|
800
1146
|
AranovaApiError,
|
|
801
1147
|
NAMED_RANGES,
|
|
1148
|
+
SALE_SOURCES,
|
|
802
1149
|
SUPPORTED_CURRENCIES,
|
|
803
1150
|
TRACKING_RANGES,
|
|
804
1151
|
createSalesClient,
|
|
@@ -807,6 +1154,7 @@ async function fetchServices(config) {
|
|
|
807
1154
|
formatDateInTz,
|
|
808
1155
|
formatMoney,
|
|
809
1156
|
fromMinor,
|
|
1157
|
+
newBookingAttemptKey,
|
|
810
1158
|
saleCreateSchema,
|
|
811
1159
|
saleItemSchema,
|
|
812
1160
|
saleServiceSchema,
|