@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/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;
@@ -628,6 +666,10 @@ function fireConversionWithConsent(input, options) {
628
666
  }
629
667
 
630
668
  // ../tracking-core/src/resources/conversion-config.ts
669
+ var BOOKING_MODES = ["live", "test", "simulated"];
670
+ function isBookingMode(value) {
671
+ return BOOKING_MODES.includes(value);
672
+ }
631
673
  function isStringMap(value) {
632
674
  return typeof value === "object" && value !== null && Object.values(value).every((v) => typeof v === "string");
633
675
  }
@@ -641,6 +683,70 @@ function parseFiring(value) {
641
683
  currency: typeof f.currency === "string" ? f.currency : null
642
684
  };
643
685
  }
686
+ function isNumberList(value) {
687
+ return Array.isArray(value) && value.every((v) => typeof v === "number");
688
+ }
689
+ function isStringList(value) {
690
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
691
+ }
692
+ function parseWindowsMap(value) {
693
+ const windowsByKey = {};
694
+ if (!value || typeof value !== "object") return windowsByKey;
695
+ for (const [key, windows] of Object.entries(value)) {
696
+ if (!windows || typeof windows !== "object") continue;
697
+ const w = windows;
698
+ windowsByKey[key] = {
699
+ morning: isNumberList(w.morning) ? w.morning : [],
700
+ afternoon: isNumberList(w.afternoon) ? w.afternoon : []
701
+ };
702
+ }
703
+ return windowsByKey;
704
+ }
705
+ function parseBookingRules(value) {
706
+ if (!value || typeof value !== "object") return null;
707
+ const r = value;
708
+ if (typeof r.min_days_ahead !== "number" || typeof r.max_days_ahead !== "number") return null;
709
+ if (!r.hours || typeof r.hours !== "object") return null;
710
+ const hours = parseWindowsMap(r.hours);
711
+ return {
712
+ min_days_ahead: r.min_days_ahead,
713
+ max_days_ahead: r.max_days_ahead,
714
+ hours,
715
+ closed_dates: isStringList(r.closed_dates) ? r.closed_dates : [],
716
+ special_hours: parseWindowsMap(r.special_hours),
717
+ vehicle_required: r.vehicle_required === true,
718
+ drop_off_options: isStringList(r.drop_off_options) ? r.drop_off_options : []
719
+ };
720
+ }
721
+ function parseBookingOption(value) {
722
+ if (!value || typeof value !== "object") return [];
723
+ const o = value;
724
+ if (typeof o.key !== "string" || !o.key || typeof o.label !== "string") return [];
725
+ return [{ key: o.key, label: o.label }];
726
+ }
727
+ function parseIntegrations(value) {
728
+ if (!value || typeof value !== "object") return {};
729
+ const autoleap = value.autoleap;
730
+ if (!autoleap || typeof autoleap !== "object") return {};
731
+ const booking = autoleap.booking;
732
+ if (!booking || typeof booking !== "object") return {};
733
+ const b = booking;
734
+ if (typeof b.fallback_url !== "string" || !b.fallback_url) return {};
735
+ return {
736
+ autoleap: {
737
+ booking: {
738
+ enabled: b.enabled === true,
739
+ fallback_url: b.fallback_url,
740
+ timezone: typeof b.timezone === "string" ? b.timezone : "UTC",
741
+ config_version: typeof b.config_version === "number" ? b.config_version : null,
742
+ accepts_bookings: b.accepts_bookings === true,
743
+ rules: parseBookingRules(b.rules),
744
+ options: Array.isArray(b.options) ? b.options.flatMap(parseBookingOption) : [],
745
+ ...isBookingMode(b.mode) ? { mode: b.mode } : {}
746
+ }
747
+ }
748
+ };
749
+ }
644
750
  function parseTrigger(value) {
645
751
  if (!value || typeof value !== "object") return null;
646
752
  const t = value;
@@ -700,7 +806,8 @@ function parseConversionConfig(raw) {
700
806
  meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
701
807
  services,
702
808
  goals,
703
- linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : []
809
+ linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
810
+ integrations: parseIntegrations(obj.integrations)
704
811
  };
705
812
  }
706
813
  var CACHE_PREFIX = "_aranova_cfg_";
@@ -1147,6 +1254,13 @@ var TrackingConfigRuntime = class {
1147
1254
  linkedHostnames() {
1148
1255
  return this.current?.linked_hostnames ?? [];
1149
1256
  }
1257
+ /**
1258
+ * Integration blocks, from the cached object even while unconfirmed: the booking fallback
1259
+ * link is safe to show stale. Same reference until the next adopt, for `useSyncExternalStore`.
1260
+ */
1261
+ integrations() {
1262
+ return this.current?.integrations ?? null;
1263
+ }
1150
1264
  async revalidateNow() {
1151
1265
  try {
1152
1266
  const headers = {};
@@ -1316,6 +1430,106 @@ function getTrackingConfigRuntime(ref, fetchImpl) {
1316
1430
  return runtime;
1317
1431
  }
1318
1432
 
1433
+ // ../../contracts/autoleap-booking-links.json
1434
+ var autoleap_booking_links_default = {
1435
+ hosts: ["myalp.io"]
1436
+ };
1437
+
1438
+ // ../tracking-core/src/integrations-public.ts
1439
+ var HANDOFF_HOSTS = new Set(autoleap_booking_links_default.hosts);
1440
+ function getBookingIntegration(integrations) {
1441
+ const booking = integrations?.autoleap?.booking;
1442
+ if (!booking) return null;
1443
+ const rules = booking.rules ? camelRules(booking.rules) : null;
1444
+ return {
1445
+ provider: "autoleap",
1446
+ enabled: booking.enabled,
1447
+ fallbackUrl: booking.fallback_url,
1448
+ acceptsBookings: booking.accepts_bookings && rules !== null && booking.config_version !== null,
1449
+ timezone: booking.timezone,
1450
+ configVersion: booking.config_version,
1451
+ rules,
1452
+ options: booking.options.map((option) => ({ key: option.key, label: option.label })),
1453
+ mode: booking.mode ?? "live"
1454
+ };
1455
+ }
1456
+ function camelRules(rules) {
1457
+ return {
1458
+ minDaysAhead: rules.min_days_ahead,
1459
+ maxDaysAhead: rules.max_days_ahead,
1460
+ hours: rules.hours,
1461
+ closedDates: rules.closed_dates,
1462
+ specialHours: rules.special_hours,
1463
+ vehicleRequired: rules.vehicle_required,
1464
+ dropOffOptions: rules.drop_off_options
1465
+ };
1466
+ }
1467
+ var WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
1468
+ function shopToday(timeZone, now = /* @__PURE__ */ new Date()) {
1469
+ const parts = new Intl.DateTimeFormat("en-CA", {
1470
+ timeZone,
1471
+ year: "numeric",
1472
+ month: "2-digit",
1473
+ day: "2-digit"
1474
+ }).formatToParts(now);
1475
+ const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
1476
+ return `${part("year")}-${part("month")}-${part("day")}`;
1477
+ }
1478
+ function addDays(isoDate, days) {
1479
+ const [year, month, day] = isoDate.split("-").map(Number);
1480
+ const date = new Date(Date.UTC(year, month - 1, day + days));
1481
+ return date.toISOString().slice(0, 10);
1482
+ }
1483
+ function bookableSlots(rules, { timeZone, now = /* @__PURE__ */ new Date() }) {
1484
+ const today = shopToday(timeZone, now);
1485
+ const days = [];
1486
+ const closed = new Set(rules.closedDates);
1487
+ for (let offset = rules.minDaysAhead; offset <= rules.maxDaysAhead; offset += 1) {
1488
+ const date = addDays(today, offset);
1489
+ if (closed.has(date)) continue;
1490
+ const weekday = WEEKDAYS[(/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay()];
1491
+ const windows = rules.specialHours[date] ?? rules.hours[weekday];
1492
+ if (!windows || windows.morning.length === 0 && windows.afternoon.length === 0) continue;
1493
+ days.push({
1494
+ date,
1495
+ weekday,
1496
+ morning: [...windows.morning],
1497
+ afternoon: [...windows.afternoon]
1498
+ });
1499
+ }
1500
+ return days;
1501
+ }
1502
+ function buildBookingHandoffUrl(fallbackUrl, prefill = {}) {
1503
+ let url;
1504
+ try {
1505
+ url = new URL(fallbackUrl);
1506
+ } catch {
1507
+ return null;
1508
+ }
1509
+ if (url.protocol !== "https:" || !HANDOFF_HOSTS.has(url.hostname) || url.username || url.password || url.port) {
1510
+ return null;
1511
+ }
1512
+ const [firstName, lastName] = prefill.firstName?.trim() ? [prefill.firstName, prefill.lastName] : splitFullName(prefill.name);
1513
+ const values = [
1514
+ ["firstName", firstName?.trim()],
1515
+ ["lastName", lastName?.trim()],
1516
+ ["phone", prefill.phone ? nationalDigits(prefill.phone) : ""],
1517
+ ["email", prefill.email?.trim()]
1518
+ ];
1519
+ for (const [key, value] of values) {
1520
+ if (value) url.searchParams.set(key, value);
1521
+ }
1522
+ return url.toString();
1523
+ }
1524
+ function splitFullName(name) {
1525
+ const [first = "", ...rest] = (name ?? "").trim().split(/\s+/);
1526
+ return [first, rest.join(" ")];
1527
+ }
1528
+ function nationalDigits(phone) {
1529
+ const parsed = parsePhone(phone);
1530
+ return (parsed.isValid ? parsed.national : phone).replace(/\D/g, "");
1531
+ }
1532
+
1319
1533
  // ../tracking-core/src/events/semantics.ts
1320
1534
  var EVENT_SEMANTICS = {
1321
1535
  page_view: {
@@ -1422,6 +1636,7 @@ var AranovaApiError = class extends Error {
1422
1636
  this.status = options.status;
1423
1637
  this.code = options.code;
1424
1638
  this.requestId = options.requestId;
1639
+ this.context = options.context;
1425
1640
  }
1426
1641
  };
1427
1642
 
@@ -1448,19 +1663,24 @@ async function apiRequest(config, method, path, body, extraHeaders) {
1448
1663
  if (!response.ok) {
1449
1664
  let detail;
1450
1665
  let code;
1666
+ let context;
1451
1667
  try {
1452
1668
  const parsed = await response.json();
1453
1669
  if (parsed && typeof parsed === "object") {
1454
1670
  const record3 = parsed;
1455
1671
  if (typeof record3.detail === "string") detail = record3.detail;
1456
1672
  if (typeof record3.code === "string") code = record3.code;
1673
+ if (record3.context && typeof record3.context === "object") {
1674
+ context = record3.context;
1675
+ }
1457
1676
  }
1458
1677
  } catch {
1459
1678
  }
1460
1679
  throw new AranovaApiError(detail ?? response.statusText ?? "Request failed", {
1461
1680
  status: response.status,
1462
1681
  code,
1463
- requestId: response.headers.get("x-request-id") ?? void 0
1682
+ requestId: response.headers.get("x-request-id") ?? void 0,
1683
+ context
1464
1684
  });
1465
1685
  }
1466
1686
  if (response.status === 204) return void 0;
@@ -1470,6 +1690,214 @@ async function apiRequest(config, method, path, body, extraHeaders) {
1470
1690
  // ../tracking-core/src/resources/sales/transport.ts
1471
1691
  var salesRequest = apiRequest;
1472
1692
 
1693
+ // ../tracking-core/src/resources/sales/bookings.ts
1694
+ var BOOKING_SIMULATE_HEADER = "X-Aranova-Simulate";
1695
+ var BOOKING_ERROR_CODES = {
1696
+ unavailable: "booking_unavailable",
1697
+ stale: "booking_config_stale",
1698
+ rejected: "booking_rejected"
1699
+ };
1700
+ var POLL_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
1701
+ var INVALID_DETAILS = "Please check your details and try again.";
1702
+ var newBookingAttemptKey = uuidV4;
1703
+ function sleep(ms) {
1704
+ return new Promise((resolve) => {
1705
+ setTimeout(resolve, ms);
1706
+ });
1707
+ }
1708
+ function outcomeOf(body) {
1709
+ const mode = body.mode ?? "live";
1710
+ if (body.status === "created")
1711
+ return { status: "created", id: body.id, requestCode: body.request_code, mode };
1712
+ if (body.status === "pending") return { status: "pending", id: body.id, mode };
1713
+ return { status: "failed", id: body.id, fallbackUrl: body.fallback_url, mode };
1714
+ }
1715
+ function formSubmitPayload(input, formId, labels) {
1716
+ const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
1717
+ const field = (name, type, label, value) => ({
1718
+ name,
1719
+ type,
1720
+ label,
1721
+ value
1722
+ });
1723
+ return {
1724
+ form: {
1725
+ id: formId,
1726
+ action: "/tracking/bookings",
1727
+ fields: [
1728
+ field("first_name", "text", "First name", input.firstName),
1729
+ field("last_name", "text", "Last name", input.lastName),
1730
+ // E.164 on the wire: it keys the customer and enhanced-conversion matching.
1731
+ field("phone", "tel", "Phone", toE164(input.phone) ?? input.phone),
1732
+ field("email", "email", "Email", input.email ?? ""),
1733
+ field(
1734
+ "service",
1735
+ "select",
1736
+ "Service",
1737
+ input.services.map((key) => labels.get(key) ?? key).join(", ")
1738
+ ),
1739
+ field("vehicle", "text", "Vehicle", vehicle),
1740
+ field(
1741
+ "start",
1742
+ "text",
1743
+ "Requested time",
1744
+ `${input.startDate} ${String(input.startHour).padStart(2, "0")}:00`
1745
+ ),
1746
+ field("note", "textarea", "Notes", input.notes ?? "")
1747
+ ]
1748
+ },
1749
+ page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
1750
+ };
1751
+ }
1752
+ function runtimeOf(firing) {
1753
+ return firing && "integrations" in firing ? firing : null;
1754
+ }
1755
+ function isLocalUrl(url) {
1756
+ try {
1757
+ return ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname);
1758
+ } catch {
1759
+ return false;
1760
+ }
1761
+ }
1762
+ function bookingSetupMismatch(endpoint, runtime) {
1763
+ if (!runtime || !isLocalUrl(endpoint)) return null;
1764
+ const configUrl = resolveTrackingConfigUrl(runtime.ref);
1765
+ if (isLocalUrl(configUrl)) return null;
1766
+ 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.`;
1767
+ }
1768
+ function createSalesBookingsClient(config, firing) {
1769
+ const submittedForms = /* @__PURE__ */ new Set();
1770
+ const staleAttempts = /* @__PURE__ */ new Set();
1771
+ let isSetupChecked = false;
1772
+ function warnOnSetupMismatch(runtime) {
1773
+ if (isSetupChecked) return;
1774
+ isSetupChecked = true;
1775
+ const warning = bookingSetupMismatch(config.endpoint, runtime);
1776
+ if (warning) console.warn(warning);
1777
+ }
1778
+ function handoff(input) {
1779
+ const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
1780
+ if (!booking) return null;
1781
+ return buildBookingHandoffUrl(booking.fallbackUrl, {
1782
+ firstName: input.firstName,
1783
+ lastName: input.lastName,
1784
+ phone: input.phone,
1785
+ email: input.email
1786
+ });
1787
+ }
1788
+ function failedBeforeSend(input) {
1789
+ const mode = getBookingIntegration(runtimeOf(firing)?.integrations())?.mode ?? "live";
1790
+ return { status: "failed", id: null, fallbackUrl: handoff(input), mode };
1791
+ }
1792
+ function withHandoff(outcome, input) {
1793
+ if (outcome.status === "failed" && !outcome.fallbackUrl) {
1794
+ return { ...outcome, fallbackUrl: handoff(input) };
1795
+ }
1796
+ return outcome;
1797
+ }
1798
+ async function poll(id, mode) {
1799
+ let outcome = { status: "pending", id, mode };
1800
+ for (const delay of POLL_DELAYS_MS) {
1801
+ await sleep(delay);
1802
+ try {
1803
+ const path = `/bookings/${encodeURIComponent(id)}`;
1804
+ outcome = outcomeOf(await salesRequest(config, "GET", path));
1805
+ } catch {
1806
+ continue;
1807
+ }
1808
+ if (outcome.status !== "pending") return outcome;
1809
+ }
1810
+ return outcome;
1811
+ }
1812
+ async function rejected(error, input, attemptKey, runtime) {
1813
+ if (!(error instanceof AranovaApiError)) {
1814
+ return failedBeforeSend(input);
1815
+ }
1816
+ if (error.code === BOOKING_ERROR_CODES.stale) {
1817
+ if (staleAttempts.has(attemptKey)) return failedBeforeSend(input);
1818
+ staleAttempts.add(attemptKey);
1819
+ await runtime?.revalidate();
1820
+ return { status: "stale", message: error.message };
1821
+ }
1822
+ if (error.code === BOOKING_ERROR_CODES.rejected) {
1823
+ const reasons = error.context?.reasons;
1824
+ return {
1825
+ status: "invalid",
1826
+ message: error.message,
1827
+ reasons: Array.isArray(reasons) ? reasons.map(String) : []
1828
+ };
1829
+ }
1830
+ if (error.code === "validation_error") {
1831
+ return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
1832
+ }
1833
+ return failedBeforeSend(input);
1834
+ }
1835
+ return {
1836
+ async submit(input, { attemptKey, formSubmit, simulate }) {
1837
+ registerCapability("booking_integration");
1838
+ const runtime = runtimeOf(firing);
1839
+ warnOnSetupMismatch(runtime);
1840
+ const booking = getBookingIntegration(runtime?.integrations());
1841
+ if (!booking?.acceptsBookings || booking.configVersion === null) {
1842
+ return failedBeforeSend(input);
1843
+ }
1844
+ if (formSubmit && !submittedForms.has(attemptKey)) {
1845
+ submittedForms.add(attemptKey);
1846
+ const labels = new Map(booking.options.map((option) => [option.key, option.label]));
1847
+ formSubmit.tracking.trackEvent(
1848
+ "form_submit",
1849
+ formSubmitPayload(input, formSubmit.formId, labels)
1850
+ );
1851
+ }
1852
+ const body = {
1853
+ idempotency_key: attemptKey,
1854
+ // The tracking client's own visit, so the booking and its form_submit share one session.
1855
+ session_id: formSubmit?.tracking.getSessionId() ?? peekSessionId() ?? `no-session-${attemptKey}`,
1856
+ config_version: booking.configVersion,
1857
+ start_date: input.startDate,
1858
+ start_hour: input.startHour,
1859
+ first_name: input.firstName.trim(),
1860
+ last_name: input.lastName.trim(),
1861
+ phone: input.phone,
1862
+ email: input.email?.trim() || null,
1863
+ vehicle: input.vehicle ?? null,
1864
+ drop_off: input.dropOff ?? null,
1865
+ services: input.services,
1866
+ notes: input.notes?.trim() || null,
1867
+ marketing_sms_consent: input.marketingSmsConsent === true,
1868
+ page_path: typeof window === "undefined" ? null : window.location.pathname
1869
+ };
1870
+ let response;
1871
+ try {
1872
+ const headers = simulate ? { [BOOKING_SIMULATE_HEADER]: simulate } : void 0;
1873
+ response = await salesRequest(
1874
+ config,
1875
+ "POST",
1876
+ "/bookings",
1877
+ body,
1878
+ headers
1879
+ );
1880
+ } catch (error) {
1881
+ return rejected(error, input, attemptKey, runtime);
1882
+ }
1883
+ const outcome = outcomeOf(response);
1884
+ return withHandoff(
1885
+ outcome.status === "pending" ? await poll(outcome.id, outcome.mode) : outcome,
1886
+ input
1887
+ );
1888
+ },
1889
+ async status(id) {
1890
+ return outcomeOf(
1891
+ await salesRequest(
1892
+ config,
1893
+ "GET",
1894
+ `/bookings/${encodeURIComponent(id)}`
1895
+ )
1896
+ );
1897
+ }
1898
+ };
1899
+ }
1900
+
1473
1901
  // ../tracking-core/src/resources/sales/client.ts
1474
1902
  function fireRecordedConversions(firing, input, recorded, sale, currency) {
1475
1903
  if (!firing) return;
@@ -1644,7 +2072,8 @@ function createSalesClient(config) {
1644
2072
  async config() {
1645
2073
  return salesRequest(config, "GET", "/business/config");
1646
2074
  }
1647
- }
2075
+ },
2076
+ bookings: createSalesBookingsClient(config, config.firing)
1648
2077
  };
1649
2078
  }
1650
2079
 
@@ -1653,6 +2082,12 @@ import * as z3 from "zod/mini";
1653
2082
 
1654
2083
  // ../tracking-core/src/resources/sales/generated/zod.gen.ts
1655
2084
  import * as z2 from "zod/mini";
2085
+ var zAutoLeapSaleFacts = z2.object({
2086
+ first_order: z2.boolean(),
2087
+ fleet_customer: z2.boolean(),
2088
+ invoice_code: z2.string(),
2089
+ invoice_id: z2.string()
2090
+ });
1656
2091
  var zErrorCode = z2.enum([
1657
2092
  "missing_credentials",
1658
2093
  "invalid_api_key",
@@ -1680,6 +2115,9 @@ var zErrorCode = z2.enum([
1680
2115
  "booking_not_reschedulable",
1681
2116
  "manage_token_invalid",
1682
2117
  "google_error",
2118
+ "booking_unavailable",
2119
+ "booking_config_stale",
2120
+ "booking_rejected",
1683
2121
  "calendar_not_owned",
1684
2122
  "calendar_is_sandbox",
1685
2123
  "sandbox_exists",
@@ -1721,6 +2159,7 @@ var zSaleServiceInputSchema = z2.strictObject({
1721
2159
  amount_cents: z2.int().check(z2.gte(0)),
1722
2160
  service: z2.string()
1723
2161
  });
2162
+ var zSaleSource = z2.enum(["api", "admin", "autoleap"]);
1724
2163
  var zSmsAutomationState = z2.enum([
1725
2164
  "needs_check",
1726
2165
  "failed",
@@ -1752,7 +2191,8 @@ var zSmsWithheldReason = z2.enum([
1752
2191
  "number_not_active",
1753
2192
  "a2p_registration_required",
1754
2193
  "alert_recipient_cannot_receive_sms",
1755
- "line_type_unresolved"
2194
+ "line_type_unresolved",
2195
+ "sale_source_sends_own_messages"
1756
2196
  ]);
1757
2197
  var zSaleSmsStatusSchema = z2.object({
1758
2198
  can_send_now: z2._default(z2.boolean(), false),
@@ -1861,6 +2301,8 @@ var zSaleEntitySchema = z2.object({
1861
2301
  sms_automation: z2.nullable(zSaleSmsStatusSchema),
1862
2302
  sms_consent: z2._default(z2.boolean(), false),
1863
2303
  sms_consent_granted_at: z2.nullable(z2.iso.datetime({ offset: true })),
2304
+ source: zSaleSource,
2305
+ source_facts: z2.nullable(zAutoLeapSaleFacts),
1864
2306
  updated_at: z2.iso.datetime({ offset: true }),
1865
2307
  writeback: z2.nullable(zSaleWritebackStatusSchema)
1866
2308
  });
@@ -1870,6 +2312,7 @@ var saleItemSchema = zSaleItemInputSchema;
1870
2312
  var saleServiceSchema = zSaleServiceInputSchema;
1871
2313
  var currencySchema = zSupportedCurrency;
1872
2314
  var SUPPORTED_CURRENCIES = currencySchema.options;
2315
+ var SALE_SOURCES = zSaleSource.options;
1873
2316
  var centsSchema = z3.int().check(z3.gte(0));
1874
2317
  var metadataSchema = z3.record(z3.string(), z3.unknown());
1875
2318
  var customerNameSchema = z3.string().check(z3.maxLength(200));
@@ -1958,9 +2401,17 @@ import {
1958
2401
  usePhoneConfig,
1959
2402
  usePhoneField
1960
2403
  } from "@aranova/tracking-react/client";
2404
+ import {
2405
+ BookingHandoffLink,
2406
+ useBookingHandoffUrl,
2407
+ useBookingIntegration,
2408
+ useBookingSlots,
2409
+ useBookingSubmit
2410
+ } from "@aranova/tracking-react/client";
1961
2411
  export {
1962
2412
  AdPlatformTracking,
1963
2413
  AranovaApiError,
2414
+ BookingHandoffLink,
1964
2415
  ConsentBanner,
1965
2416
  DEFAULT_DECLINE_TTL_DAYS,
1966
2417
  DEFAULT_PHONE_COUNTRY,
@@ -1968,9 +2419,12 @@ export {
1968
2419
  GoogleAdsTracking,
1969
2420
  NAMED_RANGES,
1970
2421
  PhoneField,
2422
+ SALE_SOURCES,
1971
2423
  SUPPORTED_CURRENCIES,
1972
2424
  TRACKING_PARAM_KEYS,
1973
2425
  TRACKING_RANGES,
2426
+ bookableSlots,
2427
+ buildBookingHandoffUrl,
1974
2428
  captureTrackingParamsFromLocation,
1975
2429
  clearStashedUserData,
1976
2430
  createSalesClient,
@@ -1985,10 +2439,12 @@ export {
1985
2439
  formatPhone,
1986
2440
  formatPhoneAsTyped,
1987
2441
  fromMinor,
2442
+ getBookingIntegration,
1988
2443
  getConsentChoice,
1989
2444
  getConsentState,
1990
2445
  getEventSemantics,
1991
2446
  getTrackingConfigRuntime,
2447
+ newBookingAttemptKey,
1992
2448
  onConsentChange,
1993
2449
  optIn,
1994
2450
  optOut,
@@ -2003,9 +2459,14 @@ export {
2003
2459
  saleUpdateSchema,
2004
2460
  salesRequest,
2005
2461
  setConsentState,
2462
+ shopToday,
2006
2463
  stashUserData,
2007
2464
  toE164,
2008
2465
  toMinor,
2466
+ useBookingHandoffUrl,
2467
+ useBookingIntegration,
2468
+ useBookingSlots,
2469
+ useBookingSubmit,
2009
2470
  useConsent,
2010
2471
  useConsentState,
2011
2472
  useCookiePreferences,