@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.mjs
CHANGED
|
@@ -170,12 +170,106 @@ function fireConversionWithConsent(input, options) {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
//
|
|
174
|
-
var
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
// ../../contracts/autoleap-booking-links.json
|
|
174
|
+
var autoleap_booking_links_default = {
|
|
175
|
+
hosts: ["myalp.io"]
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// ../tracking-core/src/integrations-public.ts
|
|
179
|
+
var HANDOFF_HOSTS = new Set(autoleap_booking_links_default.hosts);
|
|
180
|
+
function getBookingIntegration(integrations) {
|
|
181
|
+
const booking = integrations?.autoleap?.booking;
|
|
182
|
+
if (!booking) return null;
|
|
183
|
+
const rules = booking.rules ? camelRules(booking.rules) : null;
|
|
184
|
+
return {
|
|
185
|
+
provider: "autoleap",
|
|
186
|
+
enabled: booking.enabled,
|
|
187
|
+
fallbackUrl: booking.fallback_url,
|
|
188
|
+
acceptsBookings: booking.accepts_bookings && rules !== null && booking.config_version !== null,
|
|
189
|
+
timezone: booking.timezone,
|
|
190
|
+
configVersion: booking.config_version,
|
|
191
|
+
rules,
|
|
192
|
+
options: booking.options.map((option) => ({ key: option.key, label: option.label })),
|
|
193
|
+
mode: booking.mode ?? "live"
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function camelRules(rules) {
|
|
197
|
+
return {
|
|
198
|
+
minDaysAhead: rules.min_days_ahead,
|
|
199
|
+
maxDaysAhead: rules.max_days_ahead,
|
|
200
|
+
hours: rules.hours,
|
|
201
|
+
closedDates: rules.closed_dates,
|
|
202
|
+
specialHours: rules.special_hours,
|
|
203
|
+
vehicleRequired: rules.vehicle_required,
|
|
204
|
+
dropOffOptions: rules.drop_off_options
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function buildBookingHandoffUrl(fallbackUrl, prefill = {}) {
|
|
208
|
+
let url;
|
|
209
|
+
try {
|
|
210
|
+
url = new URL(fallbackUrl);
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
if (url.protocol !== "https:" || !HANDOFF_HOSTS.has(url.hostname) || url.username || url.password || url.port) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const [firstName, lastName] = prefill.firstName?.trim() ? [prefill.firstName, prefill.lastName] : splitFullName(prefill.name);
|
|
218
|
+
const values = [
|
|
219
|
+
["firstName", firstName?.trim()],
|
|
220
|
+
["lastName", lastName?.trim()],
|
|
221
|
+
["phone", prefill.phone ? nationalDigits(prefill.phone) : ""],
|
|
222
|
+
["email", prefill.email?.trim()]
|
|
223
|
+
];
|
|
224
|
+
for (const [key, value] of values) {
|
|
225
|
+
if (value) url.searchParams.set(key, value);
|
|
226
|
+
}
|
|
227
|
+
return url.toString();
|
|
228
|
+
}
|
|
229
|
+
function splitFullName(name) {
|
|
230
|
+
const [first = "", ...rest] = (name ?? "").trim().split(/\s+/);
|
|
231
|
+
return [first, rest.join(" ")];
|
|
232
|
+
}
|
|
233
|
+
function nationalDigits(phone) {
|
|
234
|
+
const parsed = parsePhone(phone);
|
|
235
|
+
return (parsed.isValid ? parsed.national : phone).replace(/\D/g, "");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ../tracking-core/src/uuid.ts
|
|
239
|
+
function uuidV4() {
|
|
240
|
+
const cryptoApi = globalThis.crypto;
|
|
241
|
+
if (typeof cryptoApi?.randomUUID === "function") return cryptoApi.randomUUID();
|
|
242
|
+
const bytes = new Uint8Array(16);
|
|
243
|
+
if (typeof cryptoApi?.getRandomValues === "function") cryptoApi.getRandomValues(bytes);
|
|
244
|
+
else for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
245
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
246
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
247
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
248
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ../tracking-core/src/session.ts
|
|
252
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
253
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
254
|
+
function readLocalStorage(key) {
|
|
255
|
+
try {
|
|
256
|
+
return window.localStorage.getItem(key);
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function peekSessionId(now = Date.now()) {
|
|
262
|
+
if (typeof window === "undefined") return null;
|
|
263
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
264
|
+
if (!raw) return null;
|
|
265
|
+
try {
|
|
266
|
+
const parsed = JSON.parse(raw);
|
|
267
|
+
if (typeof parsed.id !== "string" || typeof parsed.last_event_at !== "number") return null;
|
|
268
|
+
return now - parsed.last_event_at <= SESSION_IDLE_MS ? parsed.id : null;
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
179
273
|
|
|
180
274
|
// ../tracking-core/src/resources/http/errors.ts
|
|
181
275
|
var AranovaApiError = class extends Error {
|
|
@@ -185,9 +279,66 @@ var AranovaApiError = class extends Error {
|
|
|
185
279
|
this.status = options.status;
|
|
186
280
|
this.code = options.code;
|
|
187
281
|
this.requestId = options.requestId;
|
|
282
|
+
this.context = options.context;
|
|
188
283
|
}
|
|
189
284
|
};
|
|
190
285
|
|
|
286
|
+
// ../tracking-core/src/resources/intl/date.ts
|
|
287
|
+
function formatDateInTz(iso3, timeZone, opts, locale) {
|
|
288
|
+
const date = new Date(iso3);
|
|
289
|
+
if (Number.isNaN(date.getTime())) return iso3;
|
|
290
|
+
return new Intl.DateTimeFormat(locale, {
|
|
291
|
+
year: "numeric",
|
|
292
|
+
month: "short",
|
|
293
|
+
day: "2-digit",
|
|
294
|
+
hour: "2-digit",
|
|
295
|
+
minute: "2-digit",
|
|
296
|
+
...opts,
|
|
297
|
+
timeZone
|
|
298
|
+
}).format(date);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ../tracking-core/src/resources/sales/money.ts
|
|
302
|
+
var MINOR_UNIT_EXPONENT = {
|
|
303
|
+
USD: 2,
|
|
304
|
+
CAD: 2
|
|
305
|
+
};
|
|
306
|
+
function exponentFor(currency) {
|
|
307
|
+
return MINOR_UNIT_EXPONENT[currency] ?? 2;
|
|
308
|
+
}
|
|
309
|
+
function toMinor(amount, currency) {
|
|
310
|
+
return Math.round(amount * 10 ** exponentFor(currency));
|
|
311
|
+
}
|
|
312
|
+
function fromMinor(cents, currency) {
|
|
313
|
+
return cents / 10 ** exponentFor(currency);
|
|
314
|
+
}
|
|
315
|
+
function formatMoney(cents, currency, locale) {
|
|
316
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(
|
|
317
|
+
fromMinor(cents, currency)
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ../tracking-core/src/resources/tracking-config-runtime.ts
|
|
322
|
+
var DEFAULT_CDN_BASE_URL = "https://demos.aranova.io";
|
|
323
|
+
function trackingConfigKey(ref) {
|
|
324
|
+
if (ref.siteId) return `tracking-config/v1/sites/${ref.siteId}-${ref.environment}.json`;
|
|
325
|
+
return `tracking-config/v1/${ref.businessId}-${ref.environment}.json`;
|
|
326
|
+
}
|
|
327
|
+
function resolveTrackingConfigUrl(ref) {
|
|
328
|
+
if (ref.cdnUrl) return ref.cdnUrl;
|
|
329
|
+
const configured = ref.cdnBaseUrl?.trim();
|
|
330
|
+
const base = (configured || DEFAULT_CDN_BASE_URL).replace(/\/+$/, "");
|
|
331
|
+
return `${base}/${trackingConfigKey(ref)}`;
|
|
332
|
+
}
|
|
333
|
+
var RETRY_MAX_MS = 5 * 6e4;
|
|
334
|
+
|
|
335
|
+
// ../tracking-core/src/ingest.ts
|
|
336
|
+
var API_KEY_HEADER = "X-Aranova-Api-Key";
|
|
337
|
+
var SDK_VERSION_HEADER = "X-Aranova-Sdk-Version";
|
|
338
|
+
var SDK_PACKAGE_HEADER = "X-Aranova-Sdk-Package";
|
|
339
|
+
var SDK_SURFACE_HEADER = "X-Aranova-Sdk-Surface";
|
|
340
|
+
var SDK_ENVIRONMENT_HEADER = "X-Aranova-Sdk-Environment";
|
|
341
|
+
|
|
191
342
|
// ../tracking-core/src/resources/http/request.ts
|
|
192
343
|
function identityHeaders(config) {
|
|
193
344
|
const headers = { [API_KEY_HEADER]: config.apiKey };
|
|
@@ -211,19 +362,24 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
211
362
|
if (!response.ok) {
|
|
212
363
|
let detail;
|
|
213
364
|
let code;
|
|
365
|
+
let context;
|
|
214
366
|
try {
|
|
215
367
|
const parsed = await response.json();
|
|
216
368
|
if (parsed && typeof parsed === "object") {
|
|
217
369
|
const record3 = parsed;
|
|
218
370
|
if (typeof record3.detail === "string") detail = record3.detail;
|
|
219
371
|
if (typeof record3.code === "string") code = record3.code;
|
|
372
|
+
if (record3.context && typeof record3.context === "object") {
|
|
373
|
+
context = record3.context;
|
|
374
|
+
}
|
|
220
375
|
}
|
|
221
376
|
} catch {
|
|
222
377
|
}
|
|
223
378
|
throw new AranovaApiError(detail ?? response.statusText ?? "Request failed", {
|
|
224
379
|
status: response.status,
|
|
225
380
|
code,
|
|
226
|
-
requestId: response.headers.get("x-request-id") ?? void 0
|
|
381
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
382
|
+
context
|
|
227
383
|
});
|
|
228
384
|
}
|
|
229
385
|
if (response.status === 204) return void 0;
|
|
@@ -233,39 +389,212 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
233
389
|
// ../tracking-core/src/resources/sales/transport.ts
|
|
234
390
|
var salesRequest = apiRequest;
|
|
235
391
|
|
|
236
|
-
// ../tracking-core/src/resources/
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
month: "short",
|
|
243
|
-
day: "2-digit",
|
|
244
|
-
hour: "2-digit",
|
|
245
|
-
minute: "2-digit",
|
|
246
|
-
...opts,
|
|
247
|
-
timeZone
|
|
248
|
-
}).format(date);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ../tracking-core/src/resources/sales/money.ts
|
|
252
|
-
var MINOR_UNIT_EXPONENT = {
|
|
253
|
-
USD: 2,
|
|
254
|
-
CAD: 2
|
|
392
|
+
// ../tracking-core/src/resources/sales/bookings.ts
|
|
393
|
+
var BOOKING_SIMULATE_HEADER = "X-Aranova-Simulate";
|
|
394
|
+
var BOOKING_ERROR_CODES = {
|
|
395
|
+
unavailable: "booking_unavailable",
|
|
396
|
+
stale: "booking_config_stale",
|
|
397
|
+
rejected: "booking_rejected"
|
|
255
398
|
};
|
|
256
|
-
|
|
257
|
-
|
|
399
|
+
var POLL_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
400
|
+
var INVALID_DETAILS = "Please check your details and try again.";
|
|
401
|
+
var newBookingAttemptKey = uuidV4;
|
|
402
|
+
function sleep(ms) {
|
|
403
|
+
return new Promise((resolve) => {
|
|
404
|
+
setTimeout(resolve, ms);
|
|
405
|
+
});
|
|
258
406
|
}
|
|
259
|
-
function
|
|
260
|
-
|
|
407
|
+
function outcomeOf(body) {
|
|
408
|
+
const mode = body.mode ?? "live";
|
|
409
|
+
if (body.status === "created")
|
|
410
|
+
return { status: "created", id: body.id, requestCode: body.request_code, mode };
|
|
411
|
+
if (body.status === "pending") return { status: "pending", id: body.id, mode };
|
|
412
|
+
return { status: "failed", id: body.id, fallbackUrl: body.fallback_url, mode };
|
|
261
413
|
}
|
|
262
|
-
function
|
|
263
|
-
|
|
414
|
+
function formSubmitPayload(input, formId, labels) {
|
|
415
|
+
const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
|
|
416
|
+
const field = (name, type, label, value) => ({
|
|
417
|
+
name,
|
|
418
|
+
type,
|
|
419
|
+
label,
|
|
420
|
+
value
|
|
421
|
+
});
|
|
422
|
+
return {
|
|
423
|
+
form: {
|
|
424
|
+
id: formId,
|
|
425
|
+
action: "/tracking/bookings",
|
|
426
|
+
fields: [
|
|
427
|
+
field("first_name", "text", "First name", input.firstName),
|
|
428
|
+
field("last_name", "text", "Last name", input.lastName),
|
|
429
|
+
// E.164 on the wire: it keys the customer and enhanced-conversion matching.
|
|
430
|
+
field("phone", "tel", "Phone", toE164(input.phone) ?? input.phone),
|
|
431
|
+
field("email", "email", "Email", input.email ?? ""),
|
|
432
|
+
field(
|
|
433
|
+
"service",
|
|
434
|
+
"select",
|
|
435
|
+
"Service",
|
|
436
|
+
input.services.map((key) => labels.get(key) ?? key).join(", ")
|
|
437
|
+
),
|
|
438
|
+
field("vehicle", "text", "Vehicle", vehicle),
|
|
439
|
+
field(
|
|
440
|
+
"start",
|
|
441
|
+
"text",
|
|
442
|
+
"Requested time",
|
|
443
|
+
`${input.startDate} ${String(input.startHour).padStart(2, "0")}:00`
|
|
444
|
+
),
|
|
445
|
+
field("note", "textarea", "Notes", input.notes ?? "")
|
|
446
|
+
]
|
|
447
|
+
},
|
|
448
|
+
page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
|
|
449
|
+
};
|
|
264
450
|
}
|
|
265
|
-
function
|
|
266
|
-
return
|
|
267
|
-
|
|
268
|
-
|
|
451
|
+
function runtimeOf(firing) {
|
|
452
|
+
return firing && "integrations" in firing ? firing : null;
|
|
453
|
+
}
|
|
454
|
+
function isLocalUrl(url) {
|
|
455
|
+
try {
|
|
456
|
+
return ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname);
|
|
457
|
+
} catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function bookingSetupMismatch(endpoint, runtime) {
|
|
462
|
+
if (!runtime || !isLocalUrl(endpoint)) return null;
|
|
463
|
+
const configUrl = resolveTrackingConfigUrl(runtime.ref);
|
|
464
|
+
if (isLocalUrl(configUrl)) return null;
|
|
465
|
+
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.`;
|
|
466
|
+
}
|
|
467
|
+
function createSalesBookingsClient(config, firing) {
|
|
468
|
+
const submittedForms = /* @__PURE__ */ new Set();
|
|
469
|
+
const staleAttempts = /* @__PURE__ */ new Set();
|
|
470
|
+
let isSetupChecked = false;
|
|
471
|
+
function warnOnSetupMismatch(runtime) {
|
|
472
|
+
if (isSetupChecked) return;
|
|
473
|
+
isSetupChecked = true;
|
|
474
|
+
const warning = bookingSetupMismatch(config.endpoint, runtime);
|
|
475
|
+
if (warning) console.warn(warning);
|
|
476
|
+
}
|
|
477
|
+
function handoff(input) {
|
|
478
|
+
const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
|
|
479
|
+
if (!booking) return null;
|
|
480
|
+
return buildBookingHandoffUrl(booking.fallbackUrl, {
|
|
481
|
+
firstName: input.firstName,
|
|
482
|
+
lastName: input.lastName,
|
|
483
|
+
phone: input.phone,
|
|
484
|
+
email: input.email
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function failedBeforeSend(input) {
|
|
488
|
+
const mode = getBookingIntegration(runtimeOf(firing)?.integrations())?.mode ?? "live";
|
|
489
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input), mode };
|
|
490
|
+
}
|
|
491
|
+
function withHandoff(outcome, input) {
|
|
492
|
+
if (outcome.status === "failed" && !outcome.fallbackUrl) {
|
|
493
|
+
return { ...outcome, fallbackUrl: handoff(input) };
|
|
494
|
+
}
|
|
495
|
+
return outcome;
|
|
496
|
+
}
|
|
497
|
+
async function poll(id, mode) {
|
|
498
|
+
let outcome = { status: "pending", id, mode };
|
|
499
|
+
for (const delay of POLL_DELAYS_MS) {
|
|
500
|
+
await sleep(delay);
|
|
501
|
+
try {
|
|
502
|
+
const path = `/bookings/${encodeURIComponent(id)}`;
|
|
503
|
+
outcome = outcomeOf(await salesRequest(config, "GET", path));
|
|
504
|
+
} catch {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (outcome.status !== "pending") return outcome;
|
|
508
|
+
}
|
|
509
|
+
return outcome;
|
|
510
|
+
}
|
|
511
|
+
async function rejected(error, input, attemptKey, runtime) {
|
|
512
|
+
if (!(error instanceof AranovaApiError)) {
|
|
513
|
+
return failedBeforeSend(input);
|
|
514
|
+
}
|
|
515
|
+
if (error.code === BOOKING_ERROR_CODES.stale) {
|
|
516
|
+
if (staleAttempts.has(attemptKey)) return failedBeforeSend(input);
|
|
517
|
+
staleAttempts.add(attemptKey);
|
|
518
|
+
await runtime?.revalidate();
|
|
519
|
+
return { status: "stale", message: error.message };
|
|
520
|
+
}
|
|
521
|
+
if (error.code === BOOKING_ERROR_CODES.rejected) {
|
|
522
|
+
const reasons = error.context?.reasons;
|
|
523
|
+
return {
|
|
524
|
+
status: "invalid",
|
|
525
|
+
message: error.message,
|
|
526
|
+
reasons: Array.isArray(reasons) ? reasons.map(String) : []
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
if (error.code === "validation_error") {
|
|
530
|
+
return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
|
|
531
|
+
}
|
|
532
|
+
return failedBeforeSend(input);
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
async submit(input, { attemptKey, formSubmit, simulate }) {
|
|
536
|
+
registerCapability("booking_integration");
|
|
537
|
+
const runtime = runtimeOf(firing);
|
|
538
|
+
warnOnSetupMismatch(runtime);
|
|
539
|
+
const booking = getBookingIntegration(runtime?.integrations());
|
|
540
|
+
if (!booking?.acceptsBookings || booking.configVersion === null) {
|
|
541
|
+
return failedBeforeSend(input);
|
|
542
|
+
}
|
|
543
|
+
if (formSubmit && !submittedForms.has(attemptKey)) {
|
|
544
|
+
submittedForms.add(attemptKey);
|
|
545
|
+
const labels = new Map(booking.options.map((option) => [option.key, option.label]));
|
|
546
|
+
formSubmit.tracking.trackEvent(
|
|
547
|
+
"form_submit",
|
|
548
|
+
formSubmitPayload(input, formSubmit.formId, labels)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
const body = {
|
|
552
|
+
idempotency_key: attemptKey,
|
|
553
|
+
// The tracking client's own visit, so the booking and its form_submit share one session.
|
|
554
|
+
session_id: formSubmit?.tracking.getSessionId() ?? peekSessionId() ?? `no-session-${attemptKey}`,
|
|
555
|
+
config_version: booking.configVersion,
|
|
556
|
+
start_date: input.startDate,
|
|
557
|
+
start_hour: input.startHour,
|
|
558
|
+
first_name: input.firstName.trim(),
|
|
559
|
+
last_name: input.lastName.trim(),
|
|
560
|
+
phone: input.phone,
|
|
561
|
+
email: input.email?.trim() || null,
|
|
562
|
+
vehicle: input.vehicle ?? null,
|
|
563
|
+
drop_off: input.dropOff ?? null,
|
|
564
|
+
services: input.services,
|
|
565
|
+
notes: input.notes?.trim() || null,
|
|
566
|
+
marketing_sms_consent: input.marketingSmsConsent === true,
|
|
567
|
+
page_path: typeof window === "undefined" ? null : window.location.pathname
|
|
568
|
+
};
|
|
569
|
+
let response;
|
|
570
|
+
try {
|
|
571
|
+
const headers = simulate ? { [BOOKING_SIMULATE_HEADER]: simulate } : void 0;
|
|
572
|
+
response = await salesRequest(
|
|
573
|
+
config,
|
|
574
|
+
"POST",
|
|
575
|
+
"/bookings",
|
|
576
|
+
body,
|
|
577
|
+
headers
|
|
578
|
+
);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return rejected(error, input, attemptKey, runtime);
|
|
581
|
+
}
|
|
582
|
+
const outcome = outcomeOf(response);
|
|
583
|
+
return withHandoff(
|
|
584
|
+
outcome.status === "pending" ? await poll(outcome.id, outcome.mode) : outcome,
|
|
585
|
+
input
|
|
586
|
+
);
|
|
587
|
+
},
|
|
588
|
+
async status(id) {
|
|
589
|
+
return outcomeOf(
|
|
590
|
+
await salesRequest(
|
|
591
|
+
config,
|
|
592
|
+
"GET",
|
|
593
|
+
`/bookings/${encodeURIComponent(id)}`
|
|
594
|
+
)
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
269
598
|
}
|
|
270
599
|
|
|
271
600
|
// ../tracking-core/src/resources/sales/client.ts
|
|
@@ -442,7 +771,8 @@ function createSalesClient(config) {
|
|
|
442
771
|
async config() {
|
|
443
772
|
return salesRequest(config, "GET", "/business/config");
|
|
444
773
|
}
|
|
445
|
-
}
|
|
774
|
+
},
|
|
775
|
+
bookings: createSalesBookingsClient(config, config.firing)
|
|
446
776
|
};
|
|
447
777
|
}
|
|
448
778
|
|
|
@@ -451,6 +781,12 @@ import * as z2 from "zod/mini";
|
|
|
451
781
|
|
|
452
782
|
// ../tracking-core/src/resources/sales/generated/zod.gen.ts
|
|
453
783
|
import * as z from "zod/mini";
|
|
784
|
+
var zAutoLeapSaleFacts = z.object({
|
|
785
|
+
first_order: z.boolean(),
|
|
786
|
+
fleet_customer: z.boolean(),
|
|
787
|
+
invoice_code: z.string(),
|
|
788
|
+
invoice_id: z.string()
|
|
789
|
+
});
|
|
454
790
|
var zErrorCode = z.enum([
|
|
455
791
|
"missing_credentials",
|
|
456
792
|
"invalid_api_key",
|
|
@@ -478,6 +814,9 @@ var zErrorCode = z.enum([
|
|
|
478
814
|
"booking_not_reschedulable",
|
|
479
815
|
"manage_token_invalid",
|
|
480
816
|
"google_error",
|
|
817
|
+
"booking_unavailable",
|
|
818
|
+
"booking_config_stale",
|
|
819
|
+
"booking_rejected",
|
|
481
820
|
"calendar_not_owned",
|
|
482
821
|
"calendar_is_sandbox",
|
|
483
822
|
"sandbox_exists",
|
|
@@ -519,6 +858,7 @@ var zSaleServiceInputSchema = z.strictObject({
|
|
|
519
858
|
amount_cents: z.int().check(z.gte(0)),
|
|
520
859
|
service: z.string()
|
|
521
860
|
});
|
|
861
|
+
var zSaleSource = z.enum(["api", "admin", "autoleap"]);
|
|
522
862
|
var zSmsAutomationState = z.enum([
|
|
523
863
|
"needs_check",
|
|
524
864
|
"failed",
|
|
@@ -550,7 +890,8 @@ var zSmsWithheldReason = z.enum([
|
|
|
550
890
|
"number_not_active",
|
|
551
891
|
"a2p_registration_required",
|
|
552
892
|
"alert_recipient_cannot_receive_sms",
|
|
553
|
-
"line_type_unresolved"
|
|
893
|
+
"line_type_unresolved",
|
|
894
|
+
"sale_source_sends_own_messages"
|
|
554
895
|
]);
|
|
555
896
|
var zSaleSmsStatusSchema = z.object({
|
|
556
897
|
can_send_now: z._default(z.boolean(), false),
|
|
@@ -659,6 +1000,8 @@ var zSaleEntitySchema = z.object({
|
|
|
659
1000
|
sms_automation: z.nullable(zSaleSmsStatusSchema),
|
|
660
1001
|
sms_consent: z._default(z.boolean(), false),
|
|
661
1002
|
sms_consent_granted_at: z.nullable(z.iso.datetime({ offset: true })),
|
|
1003
|
+
source: zSaleSource,
|
|
1004
|
+
source_facts: z.nullable(zAutoLeapSaleFacts),
|
|
662
1005
|
updated_at: z.iso.datetime({ offset: true }),
|
|
663
1006
|
writeback: z.nullable(zSaleWritebackStatusSchema)
|
|
664
1007
|
});
|
|
@@ -668,6 +1011,7 @@ var saleItemSchema = zSaleItemInputSchema;
|
|
|
668
1011
|
var saleServiceSchema = zSaleServiceInputSchema;
|
|
669
1012
|
var currencySchema = zSupportedCurrency;
|
|
670
1013
|
var SUPPORTED_CURRENCIES = currencySchema.options;
|
|
1014
|
+
var SALE_SOURCES = zSaleSource.options;
|
|
671
1015
|
var centsSchema = z2.int().check(z2.gte(0));
|
|
672
1016
|
var metadataSchema = z2.record(z2.string(), z2.unknown());
|
|
673
1017
|
var customerNameSchema = z2.string().check(z2.maxLength(200));
|
|
@@ -747,6 +1091,7 @@ async function fetchServices(config) {
|
|
|
747
1091
|
export {
|
|
748
1092
|
AranovaApiError,
|
|
749
1093
|
NAMED_RANGES,
|
|
1094
|
+
SALE_SOURCES,
|
|
750
1095
|
SUPPORTED_CURRENCIES,
|
|
751
1096
|
TRACKING_RANGES,
|
|
752
1097
|
createSalesClient,
|
|
@@ -755,6 +1100,7 @@ export {
|
|
|
755
1100
|
formatDateInTz,
|
|
756
1101
|
formatMoney,
|
|
757
1102
|
fromMinor,
|
|
1103
|
+
newBookingAttemptKey,
|
|
758
1104
|
saleCreateSchema,
|
|
759
1105
|
saleItemSchema,
|
|
760
1106
|
saleServiceSchema,
|