@aranova/tracking-react 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -221,6 +221,8 @@ var TRACKING_CAPABILITIES = [
221
221
  "ad_tags_meta",
222
222
  "base_tracking",
223
223
  "blog_rendering",
224
+ "booking_handoff",
225
+ "booking_integration",
224
226
  "calendar_read",
225
227
  "calendar_write",
226
228
  "consent_controls",
@@ -500,6 +502,42 @@ function buildLandingPayloadFields(sessionId, override) {
500
502
  };
501
503
  }
502
504
 
505
+ // ../tracking-core/src/uuid.ts
506
+ function uuidV4() {
507
+ const cryptoApi = globalThis.crypto;
508
+ if (typeof cryptoApi?.randomUUID === "function") return cryptoApi.randomUUID();
509
+ const bytes = new Uint8Array(16);
510
+ if (typeof cryptoApi?.getRandomValues === "function") cryptoApi.getRandomValues(bytes);
511
+ else for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);
512
+ bytes[6] = bytes[6] & 15 | 64;
513
+ bytes[8] = bytes[8] & 63 | 128;
514
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
515
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
516
+ }
517
+
518
+ // ../tracking-core/src/session.ts
519
+ var SESSION_STORAGE_KEY = "aranova_tracking_session";
520
+ var SESSION_IDLE_MS = 30 * 60 * 1e3;
521
+ function readLocalStorage(key) {
522
+ try {
523
+ return window.localStorage.getItem(key);
524
+ } catch {
525
+ return null;
526
+ }
527
+ }
528
+ function peekSessionId(now = Date.now()) {
529
+ if (typeof window === "undefined") return null;
530
+ const raw = readLocalStorage(SESSION_STORAGE_KEY);
531
+ if (!raw) return null;
532
+ try {
533
+ const parsed = JSON.parse(raw);
534
+ if (typeof parsed.id !== "string" || typeof parsed.last_event_at !== "number") return null;
535
+ return now - parsed.last_event_at <= SESSION_IDLE_MS ? parsed.id : null;
536
+ } catch {
537
+ return null;
538
+ }
539
+ }
540
+
503
541
  // ../tracking-core/src/cross-site-link.ts
504
542
  var LINK_PARAM = "_arv";
505
543
  var LINK_TOKEN_TTL_MS = 5 * 60 * 1e3;
@@ -641,6 +679,69 @@ function parseFiring(value) {
641
679
  currency: typeof f.currency === "string" ? f.currency : null
642
680
  };
643
681
  }
682
+ function isNumberList(value) {
683
+ return Array.isArray(value) && value.every((v) => typeof v === "number");
684
+ }
685
+ function isStringList(value) {
686
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
687
+ }
688
+ function parseWindowsMap(value) {
689
+ const windowsByKey = {};
690
+ if (!value || typeof value !== "object") return windowsByKey;
691
+ for (const [key, windows] of Object.entries(value)) {
692
+ if (!windows || typeof windows !== "object") continue;
693
+ const w = windows;
694
+ windowsByKey[key] = {
695
+ morning: isNumberList(w.morning) ? w.morning : [],
696
+ afternoon: isNumberList(w.afternoon) ? w.afternoon : []
697
+ };
698
+ }
699
+ return windowsByKey;
700
+ }
701
+ function parseBookingRules(value) {
702
+ if (!value || typeof value !== "object") return null;
703
+ const r = value;
704
+ if (typeof r.min_days_ahead !== "number" || typeof r.max_days_ahead !== "number") return null;
705
+ if (!r.hours || typeof r.hours !== "object") return null;
706
+ const hours = parseWindowsMap(r.hours);
707
+ return {
708
+ min_days_ahead: r.min_days_ahead,
709
+ max_days_ahead: r.max_days_ahead,
710
+ hours,
711
+ closed_dates: isStringList(r.closed_dates) ? r.closed_dates : [],
712
+ special_hours: parseWindowsMap(r.special_hours),
713
+ vehicle_required: r.vehicle_required === true,
714
+ drop_off_options: isStringList(r.drop_off_options) ? r.drop_off_options : []
715
+ };
716
+ }
717
+ function parseBookingOption(value) {
718
+ if (!value || typeof value !== "object") return [];
719
+ const o = value;
720
+ if (typeof o.key !== "string" || !o.key || typeof o.label !== "string") return [];
721
+ return [{ key: o.key, label: o.label }];
722
+ }
723
+ function parseIntegrations(value) {
724
+ if (!value || typeof value !== "object") return {};
725
+ const autoleap = value.autoleap;
726
+ if (!autoleap || typeof autoleap !== "object") return {};
727
+ const booking = autoleap.booking;
728
+ if (!booking || typeof booking !== "object") return {};
729
+ const b = booking;
730
+ if (typeof b.fallback_url !== "string" || !b.fallback_url) return {};
731
+ return {
732
+ autoleap: {
733
+ booking: {
734
+ enabled: b.enabled === true,
735
+ fallback_url: b.fallback_url,
736
+ timezone: typeof b.timezone === "string" ? b.timezone : "UTC",
737
+ config_version: typeof b.config_version === "number" ? b.config_version : null,
738
+ accepts_bookings: b.accepts_bookings === true,
739
+ rules: parseBookingRules(b.rules),
740
+ options: Array.isArray(b.options) ? b.options.flatMap(parseBookingOption) : []
741
+ }
742
+ }
743
+ };
744
+ }
644
745
  function parseTrigger(value) {
645
746
  if (!value || typeof value !== "object") return null;
646
747
  const t = value;
@@ -700,7 +801,8 @@ function parseConversionConfig(raw) {
700
801
  meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
701
802
  services,
702
803
  goals,
703
- linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : []
804
+ linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
805
+ integrations: parseIntegrations(obj.integrations)
704
806
  };
705
807
  }
706
808
  var CACHE_PREFIX = "_aranova_cfg_";
@@ -1147,6 +1249,13 @@ var TrackingConfigRuntime = class {
1147
1249
  linkedHostnames() {
1148
1250
  return this.current?.linked_hostnames ?? [];
1149
1251
  }
1252
+ /**
1253
+ * Integration blocks, from the cached object even while unconfirmed: the booking fallback
1254
+ * link is safe to show stale. Same reference until the next adopt, for `useSyncExternalStore`.
1255
+ */
1256
+ integrations() {
1257
+ return this.current?.integrations ?? null;
1258
+ }
1150
1259
  async revalidateNow() {
1151
1260
  try {
1152
1261
  const headers = {};
@@ -1316,6 +1425,105 @@ function getTrackingConfigRuntime(ref, fetchImpl) {
1316
1425
  return runtime;
1317
1426
  }
1318
1427
 
1428
+ // ../../contracts/autoleap-booking-links.json
1429
+ var autoleap_booking_links_default = {
1430
+ hosts: ["myalp.io"]
1431
+ };
1432
+
1433
+ // ../tracking-core/src/integrations-public.ts
1434
+ var HANDOFF_HOSTS = new Set(autoleap_booking_links_default.hosts);
1435
+ function getBookingIntegration(integrations) {
1436
+ const booking = integrations?.autoleap?.booking;
1437
+ if (!booking) return null;
1438
+ const rules = booking.rules ? camelRules(booking.rules) : null;
1439
+ return {
1440
+ provider: "autoleap",
1441
+ enabled: booking.enabled,
1442
+ fallbackUrl: booking.fallback_url,
1443
+ acceptsBookings: booking.accepts_bookings && rules !== null && booking.config_version !== null,
1444
+ timezone: booking.timezone,
1445
+ configVersion: booking.config_version,
1446
+ rules,
1447
+ options: booking.options.map((option) => ({ key: option.key, label: option.label }))
1448
+ };
1449
+ }
1450
+ function camelRules(rules) {
1451
+ return {
1452
+ minDaysAhead: rules.min_days_ahead,
1453
+ maxDaysAhead: rules.max_days_ahead,
1454
+ hours: rules.hours,
1455
+ closedDates: rules.closed_dates,
1456
+ specialHours: rules.special_hours,
1457
+ vehicleRequired: rules.vehicle_required,
1458
+ dropOffOptions: rules.drop_off_options
1459
+ };
1460
+ }
1461
+ var WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
1462
+ function shopToday(timeZone, now = /* @__PURE__ */ new Date()) {
1463
+ const parts = new Intl.DateTimeFormat("en-CA", {
1464
+ timeZone,
1465
+ year: "numeric",
1466
+ month: "2-digit",
1467
+ day: "2-digit"
1468
+ }).formatToParts(now);
1469
+ const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
1470
+ return `${part("year")}-${part("month")}-${part("day")}`;
1471
+ }
1472
+ function addDays(isoDate, days) {
1473
+ const [year, month, day] = isoDate.split("-").map(Number);
1474
+ const date = new Date(Date.UTC(year, month - 1, day + days));
1475
+ return date.toISOString().slice(0, 10);
1476
+ }
1477
+ function bookableSlots(rules, { timeZone, now = /* @__PURE__ */ new Date() }) {
1478
+ const today = shopToday(timeZone, now);
1479
+ const days = [];
1480
+ const closed = new Set(rules.closedDates);
1481
+ for (let offset = rules.minDaysAhead; offset <= rules.maxDaysAhead; offset += 1) {
1482
+ const date = addDays(today, offset);
1483
+ if (closed.has(date)) continue;
1484
+ const weekday = WEEKDAYS[(/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay()];
1485
+ const windows = rules.specialHours[date] ?? rules.hours[weekday];
1486
+ if (!windows || windows.morning.length === 0 && windows.afternoon.length === 0) continue;
1487
+ days.push({
1488
+ date,
1489
+ weekday,
1490
+ morning: [...windows.morning],
1491
+ afternoon: [...windows.afternoon]
1492
+ });
1493
+ }
1494
+ return days;
1495
+ }
1496
+ function buildBookingHandoffUrl(fallbackUrl, prefill = {}) {
1497
+ let url;
1498
+ try {
1499
+ url = new URL(fallbackUrl);
1500
+ } catch {
1501
+ return null;
1502
+ }
1503
+ if (url.protocol !== "https:" || !HANDOFF_HOSTS.has(url.hostname) || url.username || url.password || url.port) {
1504
+ return null;
1505
+ }
1506
+ const [firstName, lastName] = prefill.firstName?.trim() ? [prefill.firstName, prefill.lastName] : splitFullName(prefill.name);
1507
+ const values = [
1508
+ ["firstName", firstName?.trim()],
1509
+ ["lastName", lastName?.trim()],
1510
+ ["phone", prefill.phone ? nationalDigits(prefill.phone) : ""],
1511
+ ["email", prefill.email?.trim()]
1512
+ ];
1513
+ for (const [key, value] of values) {
1514
+ if (value) url.searchParams.set(key, value);
1515
+ }
1516
+ return url.toString();
1517
+ }
1518
+ function splitFullName(name) {
1519
+ const [first = "", ...rest] = (name ?? "").trim().split(/\s+/);
1520
+ return [first, rest.join(" ")];
1521
+ }
1522
+ function nationalDigits(phone) {
1523
+ const parsed = parsePhone(phone);
1524
+ return (parsed.isValid ? parsed.national : phone).replace(/\D/g, "");
1525
+ }
1526
+
1319
1527
  // ../tracking-core/src/events/semantics.ts
1320
1528
  var EVENT_SEMANTICS = {
1321
1529
  page_view: {
@@ -1422,6 +1630,7 @@ var AranovaApiError = class extends Error {
1422
1630
  this.status = options.status;
1423
1631
  this.code = options.code;
1424
1632
  this.requestId = options.requestId;
1633
+ this.context = options.context;
1425
1634
  }
1426
1635
  };
1427
1636
 
@@ -1448,19 +1657,24 @@ async function apiRequest(config, method, path, body, extraHeaders) {
1448
1657
  if (!response.ok) {
1449
1658
  let detail;
1450
1659
  let code;
1660
+ let context;
1451
1661
  try {
1452
1662
  const parsed = await response.json();
1453
1663
  if (parsed && typeof parsed === "object") {
1454
1664
  const record3 = parsed;
1455
1665
  if (typeof record3.detail === "string") detail = record3.detail;
1456
1666
  if (typeof record3.code === "string") code = record3.code;
1667
+ if (record3.context && typeof record3.context === "object") {
1668
+ context = record3.context;
1669
+ }
1457
1670
  }
1458
1671
  } catch {
1459
1672
  }
1460
1673
  throw new AranovaApiError(detail ?? response.statusText ?? "Request failed", {
1461
1674
  status: response.status,
1462
1675
  code,
1463
- requestId: response.headers.get("x-request-id") ?? void 0
1676
+ requestId: response.headers.get("x-request-id") ?? void 0,
1677
+ context
1464
1678
  });
1465
1679
  }
1466
1680
  if (response.status === 204) return void 0;
@@ -1470,6 +1684,179 @@ async function apiRequest(config, method, path, body, extraHeaders) {
1470
1684
  // ../tracking-core/src/resources/sales/transport.ts
1471
1685
  var salesRequest = apiRequest;
1472
1686
 
1687
+ // ../tracking-core/src/resources/sales/bookings.ts
1688
+ var BOOKING_ERROR_CODES = {
1689
+ unavailable: "booking_unavailable",
1690
+ stale: "booking_config_stale",
1691
+ rejected: "booking_rejected"
1692
+ };
1693
+ var POLL_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
1694
+ var INVALID_DETAILS = "Please check your details and try again.";
1695
+ var newBookingAttemptKey = uuidV4;
1696
+ function sleep(ms) {
1697
+ return new Promise((resolve) => {
1698
+ setTimeout(resolve, ms);
1699
+ });
1700
+ }
1701
+ function outcomeOf(body) {
1702
+ if (body.status === "created")
1703
+ return { status: "created", id: body.id, requestCode: body.request_code };
1704
+ if (body.status === "pending") return { status: "pending", id: body.id };
1705
+ return { status: "failed", id: body.id, fallbackUrl: body.fallback_url };
1706
+ }
1707
+ function formSubmitPayload(input, formId, labels) {
1708
+ const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
1709
+ const field = (name, type, label, value) => ({
1710
+ name,
1711
+ type,
1712
+ label,
1713
+ value
1714
+ });
1715
+ return {
1716
+ form: {
1717
+ id: formId,
1718
+ action: "/tracking/bookings",
1719
+ fields: [
1720
+ field("first_name", "text", "First name", input.firstName),
1721
+ field("last_name", "text", "Last name", input.lastName),
1722
+ // E.164 on the wire: it keys the customer and enhanced-conversion matching.
1723
+ field("phone", "tel", "Phone", toE164(input.phone) ?? input.phone),
1724
+ field("email", "email", "Email", input.email ?? ""),
1725
+ field(
1726
+ "service",
1727
+ "select",
1728
+ "Service",
1729
+ input.services.map((key) => labels.get(key) ?? key).join(", ")
1730
+ ),
1731
+ field("vehicle", "text", "Vehicle", vehicle),
1732
+ field(
1733
+ "start",
1734
+ "text",
1735
+ "Requested time",
1736
+ `${input.startDate} ${String(input.startHour).padStart(2, "0")}:00`
1737
+ ),
1738
+ field("note", "textarea", "Notes", input.notes ?? "")
1739
+ ]
1740
+ },
1741
+ page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
1742
+ };
1743
+ }
1744
+ function runtimeOf(firing) {
1745
+ return firing && "integrations" in firing ? firing : null;
1746
+ }
1747
+ function createSalesBookingsClient(config, firing) {
1748
+ const submittedForms = /* @__PURE__ */ new Set();
1749
+ const staleAttempts = /* @__PURE__ */ new Set();
1750
+ function handoff(input) {
1751
+ const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
1752
+ if (!booking) return null;
1753
+ return buildBookingHandoffUrl(booking.fallbackUrl, {
1754
+ firstName: input.firstName,
1755
+ lastName: input.lastName,
1756
+ phone: input.phone,
1757
+ email: input.email
1758
+ });
1759
+ }
1760
+ function withHandoff(outcome, input) {
1761
+ if (outcome.status === "failed" && !outcome.fallbackUrl) {
1762
+ return { ...outcome, fallbackUrl: handoff(input) };
1763
+ }
1764
+ return outcome;
1765
+ }
1766
+ async function poll(id) {
1767
+ let outcome = { status: "pending", id };
1768
+ for (const delay of POLL_DELAYS_MS) {
1769
+ await sleep(delay);
1770
+ try {
1771
+ const path = `/bookings/${encodeURIComponent(id)}`;
1772
+ outcome = outcomeOf(await salesRequest(config, "GET", path));
1773
+ } catch {
1774
+ continue;
1775
+ }
1776
+ if (outcome.status !== "pending") return outcome;
1777
+ }
1778
+ return outcome;
1779
+ }
1780
+ async function rejected(error, input, attemptKey, runtime) {
1781
+ if (!(error instanceof AranovaApiError)) {
1782
+ return { status: "failed", id: null, fallbackUrl: handoff(input) };
1783
+ }
1784
+ if (error.code === BOOKING_ERROR_CODES.stale) {
1785
+ if (staleAttempts.has(attemptKey)) {
1786
+ return { status: "failed", id: null, fallbackUrl: handoff(input) };
1787
+ }
1788
+ staleAttempts.add(attemptKey);
1789
+ await runtime?.revalidate();
1790
+ return { status: "stale", message: error.message };
1791
+ }
1792
+ if (error.code === BOOKING_ERROR_CODES.rejected) {
1793
+ const reasons = error.context?.reasons;
1794
+ return {
1795
+ status: "invalid",
1796
+ message: error.message,
1797
+ reasons: Array.isArray(reasons) ? reasons.map(String) : []
1798
+ };
1799
+ }
1800
+ if (error.code === "validation_error") {
1801
+ return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
1802
+ }
1803
+ return { status: "failed", id: null, fallbackUrl: handoff(input) };
1804
+ }
1805
+ return {
1806
+ async submit(input, { attemptKey, formSubmit }) {
1807
+ registerCapability("booking_integration");
1808
+ const runtime = runtimeOf(firing);
1809
+ const booking = getBookingIntegration(runtime?.integrations());
1810
+ if (!booking?.acceptsBookings || booking.configVersion === null) {
1811
+ return { status: "failed", id: null, fallbackUrl: handoff(input) };
1812
+ }
1813
+ if (formSubmit && !submittedForms.has(attemptKey)) {
1814
+ submittedForms.add(attemptKey);
1815
+ const labels = new Map(booking.options.map((option) => [option.key, option.label]));
1816
+ formSubmit.tracking.trackEvent(
1817
+ "form_submit",
1818
+ formSubmitPayload(input, formSubmit.formId, labels)
1819
+ );
1820
+ }
1821
+ const body = {
1822
+ idempotency_key: attemptKey,
1823
+ // The tracking client's own visit, so the booking and its form_submit share one session.
1824
+ session_id: formSubmit?.tracking.getSessionId() ?? peekSessionId() ?? `no-session-${attemptKey}`,
1825
+ config_version: booking.configVersion,
1826
+ start_date: input.startDate,
1827
+ start_hour: input.startHour,
1828
+ first_name: input.firstName.trim(),
1829
+ last_name: input.lastName.trim(),
1830
+ phone: input.phone,
1831
+ email: input.email?.trim() || null,
1832
+ vehicle: input.vehicle ?? null,
1833
+ drop_off: input.dropOff ?? null,
1834
+ services: input.services,
1835
+ notes: input.notes?.trim() || null,
1836
+ marketing_sms_consent: input.marketingSmsConsent === true,
1837
+ page_path: typeof window === "undefined" ? null : window.location.pathname
1838
+ };
1839
+ let response;
1840
+ try {
1841
+ response = await salesRequest(config, "POST", "/bookings", body);
1842
+ } catch (error) {
1843
+ return rejected(error, input, attemptKey, runtime);
1844
+ }
1845
+ const outcome = outcomeOf(response);
1846
+ return withHandoff(outcome.status === "pending" ? await poll(outcome.id) : outcome, input);
1847
+ },
1848
+ async status(id) {
1849
+ return outcomeOf(
1850
+ await salesRequest(
1851
+ config,
1852
+ "GET",
1853
+ `/bookings/${encodeURIComponent(id)}`
1854
+ )
1855
+ );
1856
+ }
1857
+ };
1858
+ }
1859
+
1473
1860
  // ../tracking-core/src/resources/sales/client.ts
1474
1861
  function fireRecordedConversions(firing, input, recorded, sale, currency) {
1475
1862
  if (!firing) return;
@@ -1644,7 +2031,8 @@ function createSalesClient(config) {
1644
2031
  async config() {
1645
2032
  return salesRequest(config, "GET", "/business/config");
1646
2033
  }
1647
- }
2034
+ },
2035
+ bookings: createSalesBookingsClient(config, config.firing)
1648
2036
  };
1649
2037
  }
1650
2038
 
@@ -1653,6 +2041,12 @@ import * as z3 from "zod/mini";
1653
2041
 
1654
2042
  // ../tracking-core/src/resources/sales/generated/zod.gen.ts
1655
2043
  import * as z2 from "zod/mini";
2044
+ var zAutoLeapSaleFacts = z2.object({
2045
+ first_order: z2.boolean(),
2046
+ fleet_customer: z2.boolean(),
2047
+ invoice_code: z2.string(),
2048
+ invoice_id: z2.string()
2049
+ });
1656
2050
  var zErrorCode = z2.enum([
1657
2051
  "missing_credentials",
1658
2052
  "invalid_api_key",
@@ -1680,6 +2074,9 @@ var zErrorCode = z2.enum([
1680
2074
  "booking_not_reschedulable",
1681
2075
  "manage_token_invalid",
1682
2076
  "google_error",
2077
+ "booking_unavailable",
2078
+ "booking_config_stale",
2079
+ "booking_rejected",
1683
2080
  "calendar_not_owned",
1684
2081
  "calendar_is_sandbox",
1685
2082
  "sandbox_exists",
@@ -1721,6 +2118,7 @@ var zSaleServiceInputSchema = z2.strictObject({
1721
2118
  amount_cents: z2.int().check(z2.gte(0)),
1722
2119
  service: z2.string()
1723
2120
  });
2121
+ var zSaleSource = z2.enum(["api", "admin", "autoleap"]);
1724
2122
  var zSmsAutomationState = z2.enum([
1725
2123
  "needs_check",
1726
2124
  "failed",
@@ -1752,7 +2150,8 @@ var zSmsWithheldReason = z2.enum([
1752
2150
  "number_not_active",
1753
2151
  "a2p_registration_required",
1754
2152
  "alert_recipient_cannot_receive_sms",
1755
- "line_type_unresolved"
2153
+ "line_type_unresolved",
2154
+ "sale_source_sends_own_messages"
1756
2155
  ]);
1757
2156
  var zSaleSmsStatusSchema = z2.object({
1758
2157
  can_send_now: z2._default(z2.boolean(), false),
@@ -1861,6 +2260,8 @@ var zSaleEntitySchema = z2.object({
1861
2260
  sms_automation: z2.nullable(zSaleSmsStatusSchema),
1862
2261
  sms_consent: z2._default(z2.boolean(), false),
1863
2262
  sms_consent_granted_at: z2.nullable(z2.iso.datetime({ offset: true })),
2263
+ source: zSaleSource,
2264
+ source_facts: z2.nullable(zAutoLeapSaleFacts),
1864
2265
  updated_at: z2.iso.datetime({ offset: true }),
1865
2266
  writeback: z2.nullable(zSaleWritebackStatusSchema)
1866
2267
  });
@@ -1870,6 +2271,7 @@ var saleItemSchema = zSaleItemInputSchema;
1870
2271
  var saleServiceSchema = zSaleServiceInputSchema;
1871
2272
  var currencySchema = zSupportedCurrency;
1872
2273
  var SUPPORTED_CURRENCIES = currencySchema.options;
2274
+ var SALE_SOURCES = zSaleSource.options;
1873
2275
  var centsSchema = z3.int().check(z3.gte(0));
1874
2276
  var metadataSchema = z3.record(z3.string(), z3.unknown());
1875
2277
  var customerNameSchema = z3.string().check(z3.maxLength(200));
@@ -1958,9 +2360,17 @@ import {
1958
2360
  usePhoneConfig,
1959
2361
  usePhoneField
1960
2362
  } from "@aranova/tracking-react/client";
2363
+ import {
2364
+ BookingHandoffLink,
2365
+ useBookingHandoffUrl,
2366
+ useBookingIntegration,
2367
+ useBookingSlots,
2368
+ useBookingSubmit
2369
+ } from "@aranova/tracking-react/client";
1961
2370
  export {
1962
2371
  AdPlatformTracking,
1963
2372
  AranovaApiError,
2373
+ BookingHandoffLink,
1964
2374
  ConsentBanner,
1965
2375
  DEFAULT_DECLINE_TTL_DAYS,
1966
2376
  DEFAULT_PHONE_COUNTRY,
@@ -1968,9 +2378,12 @@ export {
1968
2378
  GoogleAdsTracking,
1969
2379
  NAMED_RANGES,
1970
2380
  PhoneField,
2381
+ SALE_SOURCES,
1971
2382
  SUPPORTED_CURRENCIES,
1972
2383
  TRACKING_PARAM_KEYS,
1973
2384
  TRACKING_RANGES,
2385
+ bookableSlots,
2386
+ buildBookingHandoffUrl,
1974
2387
  captureTrackingParamsFromLocation,
1975
2388
  clearStashedUserData,
1976
2389
  createSalesClient,
@@ -1985,10 +2398,12 @@ export {
1985
2398
  formatPhone,
1986
2399
  formatPhoneAsTyped,
1987
2400
  fromMinor,
2401
+ getBookingIntegration,
1988
2402
  getConsentChoice,
1989
2403
  getConsentState,
1990
2404
  getEventSemantics,
1991
2405
  getTrackingConfigRuntime,
2406
+ newBookingAttemptKey,
1992
2407
  onConsentChange,
1993
2408
  optIn,
1994
2409
  optOut,
@@ -2003,9 +2418,14 @@ export {
2003
2418
  saleUpdateSchema,
2004
2419
  salesRequest,
2005
2420
  setConsentState,
2421
+ shopToday,
2006
2422
  stashUserData,
2007
2423
  toE164,
2008
2424
  toMinor,
2425
+ useBookingHandoffUrl,
2426
+ useBookingIntegration,
2427
+ useBookingSlots,
2428
+ useBookingSubmit,
2009
2429
  useConsent,
2010
2430
  useConsentState,
2011
2431
  useCookiePreferences,