@aranova/tracking-next 0.20.2 → 0.22.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.
Files changed (48) hide show
  1. package/README.md +80 -1
  2. package/dist/blog-server.js +12 -0
  3. package/dist/blog-server.js.map +1 -1
  4. package/dist/blog-server.mjs +12 -0
  5. package/dist/blog-server.mjs.map +1 -1
  6. package/dist/blog.js +7 -0
  7. package/dist/blog.js.map +1 -1
  8. package/dist/blog.mjs +7 -0
  9. package/dist/blog.mjs.map +1 -1
  10. package/dist/calendar-server.d.mts +53 -0
  11. package/dist/calendar-server.d.ts +53 -0
  12. package/dist/calendar-server.js +206 -0
  13. package/dist/calendar-server.js.map +1 -0
  14. package/dist/calendar-server.mjs +180 -0
  15. package/dist/calendar-server.mjs.map +1 -0
  16. package/dist/calendar.d.mts +112 -0
  17. package/dist/calendar.d.ts +112 -0
  18. package/dist/calendar.js +524 -0
  19. package/dist/calendar.js.map +1 -0
  20. package/dist/calendar.mjs +464 -0
  21. package/dist/calendar.mjs.map +1 -0
  22. package/dist/date-BHetnL7s.d.mts +26 -0
  23. package/dist/date-BHetnL7s.d.ts +26 -0
  24. package/dist/index.d.mts +62 -5
  25. package/dist/index.d.ts +62 -5
  26. package/dist/index.js +566 -21
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +529 -21
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/{phone-utils-B2vHJkNz.d.mts → phone-utils-BhWfNuPS.d.mts} +8 -0
  31. package/dist/{phone-utils-B2vHJkNz.d.ts → phone-utils-BhWfNuPS.d.ts} +8 -0
  32. package/dist/phone.d.mts +1 -1
  33. package/dist/phone.d.ts +1 -1
  34. package/dist/request-Doq36Rt0.d.mts +19 -0
  35. package/dist/request-Doq36Rt0.d.ts +19 -0
  36. package/dist/{sales-DrH6dY5F.d.mts → sales-C7tPnqbq.d.ts} +9 -48
  37. package/dist/{sales-DrH6dY5F.d.ts → sales-H_6bHrL-.d.mts} +9 -48
  38. package/dist/sales.d.mts +3 -1
  39. package/dist/sales.d.ts +3 -1
  40. package/dist/sales.js +29 -17
  41. package/dist/sales.js.map +1 -1
  42. package/dist/sales.mjs +29 -17
  43. package/dist/sales.mjs.map +1 -1
  44. package/dist/server.d.mts +1 -1
  45. package/dist/server.d.ts +1 -1
  46. package/dist/transport-C-_rFSR4.d.ts +721 -0
  47. package/dist/transport-yUF5Wize.d.mts +721 -0
  48. package/package.json +21 -1
package/dist/index.js CHANGED
@@ -32,39 +32,71 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AdPlatformTracking: () => AdPlatformTracking,
34
34
  AranovaApiError: () => AranovaApiError,
35
+ BOOKING_SOURCES: () => BOOKING_SOURCES,
36
+ BOOKING_STATUSES: () => BOOKING_STATUSES,
37
+ CALENDAR_ERROR_CODES: () => CALENDAR_ERROR_CODES,
38
+ CALENDAR_METADATA_SCHEMA_VERSION: () => CALENDAR_METADATA_SCHEMA_VERSION,
39
+ CALENDAR_SYNC_STATUSES: () => CALENDAR_SYNC_STATUSES,
40
+ CalendarClientProvider: () => CalendarClientProvider,
35
41
  ConsentBanner: () => ConsentBanner,
36
42
  DEFAULT_DECLINE_TTL_DAYS: () => DEFAULT_DECLINE_TTL_DAYS,
37
43
  DEFAULT_PHONE_COUNTRY: () => DEFAULT_PHONE_COUNTRY,
38
44
  EVENT_SEMANTICS: () => EVENT_SEMANTICS,
45
+ GOOGLE_SYNC_STATES: () => GOOGLE_SYNC_STATES,
39
46
  GoogleAdsTracking: () => GoogleAdsTracking,
47
+ MAX_ATTENDEES: () => MAX_ATTENDEES,
48
+ MAX_BOOKING_MINUTES: () => MAX_BOOKING_MINUTES,
49
+ MAX_REMINDER_MINUTES: () => MAX_REMINDER_MINUTES,
50
+ MAX_REMINDER_OVERRIDES: () => MAX_REMINDER_OVERRIDES,
40
51
  NAMED_RANGES: () => NAMED_RANGES,
41
52
  PhoneField: () => PhoneField,
42
53
  SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES,
43
54
  TRACKING_PARAM_KEYS: () => TRACKING_PARAM_KEYS,
44
55
  TRACKING_RANGES: () => TRACKING_RANGES,
56
+ bookingAttendeeSchema: () => bookingAttendeeSchema,
57
+ bookingCancelSchema: () => bookingCancelSchema,
58
+ bookingCreateSchema: () => bookingCreateSchema,
59
+ bookingCustomerSchema: () => bookingCustomerSchema,
60
+ bookingLocationNullableSchema: () => bookingLocationNullableSchema,
61
+ bookingLocationSchema: () => bookingLocationSchema,
62
+ bookingMetadataSchema: () => bookingMetadataSchema,
63
+ bookingReminderSchema: () => bookingReminderSchema,
64
+ bookingRemindersSchema: () => bookingRemindersSchema,
65
+ bookingRescheduleSchema: () => bookingRescheduleSchema,
45
66
  captureTrackingParamsFromLocation: () => captureTrackingParamsFromLocation,
46
67
  clearStashedUserData: () => clearStashedUserData,
68
+ computeSlots: () => computeSlots,
69
+ createCalendarReadClient: () => createCalendarReadClient,
47
70
  createSalesClient: () => createSalesClient,
48
71
  createTracking: () => createTracking,
49
72
  createTrackingClientContext: () => createTrackingClientContext,
50
73
  createTrackingEventCreatePayload: () => createTrackingEventCreatePayload,
51
74
  createTrackingSessionUpsertPayload: () => createTrackingSessionUpsertPayload,
75
+ durationMinutes: () => durationMinutes,
76
+ endOfDayInTz: () => endOfDayInTz,
52
77
  fetchServices: () => fetchServices,
53
78
  formatDateInTz: () => formatDateInTz,
54
79
  formatMoney: () => formatMoney,
55
80
  formatPhone: () => formatPhone,
56
81
  formatPhoneAsTyped: () => formatPhoneAsTyped,
82
+ formatSlotRange: () => formatSlotRange,
57
83
  fromMinor: () => fromMinor,
58
84
  getConsentChoice: () => getConsentChoice,
59
85
  getConsentState: () => getConsentState,
60
86
  getEventSemantics: () => getEventSemantics,
61
87
  getTrackingConfigRuntime: () => getTrackingConfigRuntime,
88
+ groupSlotsByDay: () => groupSlotsByDay,
89
+ instantFromWallClock: () => instantFromWallClock,
90
+ isCalendarError: () => isCalendarError,
91
+ localDateKey: () => localDateKey,
92
+ offsetMinutesAt: () => offsetMinutesAt,
62
93
  onConsentChange: () => onConsentChange,
63
94
  optIn: () => optIn,
64
95
  optOut: () => optOut,
65
96
  parsePhone: () => parsePhone,
66
97
  phoneField: () => phoneField,
67
98
  resetConsent: () => resetConsent,
99
+ resolveBrowserTimeZone: () => resolveBrowserTimeZone,
68
100
  resolveConversionConfig: () => resolveConversionConfig,
69
101
  saleCreateSchema: () => saleCreateSchema,
70
102
  saleItemSchema: () => saleItemSchema,
@@ -72,15 +104,20 @@ __export(src_exports, {
72
104
  saleUpdateSchema: () => saleUpdateSchema,
73
105
  salesRequest: () => salesRequest,
74
106
  setConsentState: () => setConsentState,
107
+ startOfDayInTz: () => startOfDayInTz,
75
108
  stashUserData: () => stashUserData,
76
109
  toE164: () => toE164,
77
110
  toMinor: () => toMinor,
111
+ useBookingForm: () => useBookingForm,
112
+ useCalendarBusy: () => useCalendarBusy,
113
+ useCalendarClient: () => useCalendarClient,
78
114
  useConsent: () => useConsent,
79
115
  useConsentState: () => useConsentState,
80
116
  useCookiePreferences: () => useCookiePreferences,
81
117
  useGclid: () => useGclid,
82
118
  usePhoneConfig: () => usePhoneConfig,
83
119
  usePhoneField: () => usePhoneField,
120
+ useSlots: () => useSlots,
84
121
  useTrackingParams: () => useTrackingParams
85
122
  });
86
123
  module.exports = __toCommonJS(src_exports);
@@ -345,6 +382,43 @@ var aranovaConsent = (function () {
345
382
  `.trim();
346
383
  }
347
384
 
385
+ // ../tracking-core/src/capabilities.ts
386
+ var TRACKING_CAPABILITIES = [
387
+ "ad_tags_google",
388
+ "ad_tags_meta",
389
+ "base_tracking",
390
+ "blog_rendering",
391
+ "calendar_read",
392
+ "calendar_write",
393
+ "consent_controls",
394
+ "conversion_goals_auto",
395
+ "conversion_goals_manual",
396
+ "cta_click_capture",
397
+ "form_capture",
398
+ "phone_click_capture",
399
+ "phone_fields"
400
+ ];
401
+ var CAPABILITY_DOM_ATTRIBUTE = "data-aranova-capability";
402
+ var registered = /* @__PURE__ */ new Set();
403
+ function registerCapability(capability) {
404
+ registered.add(capability);
405
+ }
406
+ function getRegisteredCapabilities() {
407
+ const all = new Set(registered);
408
+ for (const marker of readDomMarkers()) all.add(marker);
409
+ return Array.from(all).sort();
410
+ }
411
+ function readDomMarkers() {
412
+ if (typeof document === "undefined") return [];
413
+ const known = new Set(TRACKING_CAPABILITIES);
414
+ const found = [];
415
+ for (const node of document.querySelectorAll(`[${CAPABILITY_DOM_ATTRIBUTE}]`)) {
416
+ const value = node.getAttribute(CAPABILITY_DOM_ATTRIBUTE);
417
+ if (value && known.has(value)) found.push(value);
418
+ }
419
+ return found;
420
+ }
421
+
348
422
  // ../tracking-core/src/tracking.ts
349
423
  var TRACKING_COOKIE_MAX_AGE_SECONDS = 7776e3;
350
424
  var TRACKING_PARAM_KEYS = [
@@ -689,7 +763,8 @@ function createTrackingClientContext(surface, input = {}) {
689
763
  page_title: input.pageTitle ?? (typeof document === "undefined" ? null : document.title || null),
690
764
  referrer: input.referrer ?? (typeof document === "undefined" ? null : document.referrer || null),
691
765
  environment: input.environment ?? "production",
692
- active_gtag_ids: input.activeGtagIds ?? null
766
+ active_gtag_ids: input.activeGtagIds ?? null,
767
+ capabilities: getRegisteredCapabilities()
693
768
  };
694
769
  }
695
770
  function createTrackingSessionUpsertPayload(trackingParams, input, context) {
@@ -1033,6 +1108,21 @@ function automaticThresholdMet(goal, eventType, metadata) {
1033
1108
  }
1034
1109
  }
1035
1110
 
1111
+ // ../tracking-core/src/resources/intl/date.ts
1112
+ function formatDateInTz(iso, timeZone, opts, locale) {
1113
+ const date = new Date(iso);
1114
+ if (Number.isNaN(date.getTime())) return iso;
1115
+ return new Intl.DateTimeFormat(locale, {
1116
+ year: "numeric",
1117
+ month: "short",
1118
+ day: "2-digit",
1119
+ hour: "2-digit",
1120
+ minute: "2-digit",
1121
+ ...opts,
1122
+ timeZone
1123
+ }).format(date);
1124
+ }
1125
+
1036
1126
  // ../tracking-core/src/resources/sales/money.ts
1037
1127
  var MINOR_UNIT_EXPONENT = {
1038
1128
  USD: 2,
@@ -1052,19 +1142,6 @@ function formatMoney(cents, currency, locale) {
1052
1142
  fromMinor(cents, currency)
1053
1143
  );
1054
1144
  }
1055
- function formatDateInTz(iso, timeZone, opts, locale) {
1056
- const date = new Date(iso);
1057
- if (Number.isNaN(date.getTime())) return iso;
1058
- return new Intl.DateTimeFormat(locale, {
1059
- year: "numeric",
1060
- month: "short",
1061
- day: "2-digit",
1062
- hour: "2-digit",
1063
- minute: "2-digit",
1064
- ...opts,
1065
- timeZone
1066
- }).format(date);
1067
- }
1068
1145
 
1069
1146
  // ../tracking-core/src/resources/tracking-config-runtime.ts
1070
1147
  var CACHE_PREFIX2 = "_aranova_cfg_runtime_";
@@ -1681,7 +1758,10 @@ function buildContext(surface, sdkVersion, packageName, environment, activeGtagI
1681
1758
  page_title: typeof document === "undefined" ? null : document.title || null,
1682
1759
  referrer: typeof document === "undefined" ? null : document.referrer || null,
1683
1760
  environment,
1684
- active_gtag_ids: activeGtagIds
1761
+ active_gtag_ids: activeGtagIds,
1762
+ // Rebuilt per flush (this runs inside the payload builder), so a client
1763
+ // constructed later on a deeper route still gets reported.
1764
+ capabilities: getRegisteredCapabilities()
1685
1765
  };
1686
1766
  }
1687
1767
  function readTrackingParams() {
@@ -2274,8 +2354,9 @@ function getEventDefinition(name) {
2274
2354
  }
2275
2355
 
2276
2356
  // ../tracking-core/src/ingest-typed.ts
2277
- function createTypedClient(raw, _registry, options = {}) {
2357
+ function createTypedClient(raw, registry, options = {}) {
2278
2358
  const debug = options.debug ?? false;
2359
+ if (registry.manual?.form_submit) registerCapability("form_capture");
2279
2360
  return {
2280
2361
  trackEvent(eventType, metadata, opts) {
2281
2362
  if (debug) {
@@ -2682,6 +2763,7 @@ function attachCtaClickCapture(client, config) {
2682
2763
  return () => {
2683
2764
  };
2684
2765
  }
2766
+ registerCapability("cta_click_capture");
2685
2767
  const selector = autoCapture.selector ?? DEFAULT_CTA_SELECTOR;
2686
2768
  function onClick(event) {
2687
2769
  const target = event.target;
@@ -2738,6 +2820,7 @@ function attachPhoneClickCapture(client, config) {
2738
2820
  return () => {
2739
2821
  };
2740
2822
  }
2823
+ registerCapability("phone_click_capture");
2741
2824
  const selector = autoCapture.selector ?? DEFAULT_TEL_SELECTOR;
2742
2825
  function onClick(event) {
2743
2826
  const target = event.target;
@@ -2767,7 +2850,7 @@ function attachPhoneClickCapture(client, config) {
2767
2850
  };
2768
2851
  }
2769
2852
 
2770
- // ../tracking-core/src/resources/sales/errors.ts
2853
+ // ../tracking-core/src/resources/http/errors.ts
2771
2854
  var AranovaApiError = class extends Error {
2772
2855
  constructor(message, options) {
2773
2856
  super(message);
@@ -2778,7 +2861,7 @@ var AranovaApiError = class extends Error {
2778
2861
  }
2779
2862
  };
2780
2863
 
2781
- // ../tracking-core/src/resources/sales/transport.ts
2864
+ // ../tracking-core/src/resources/http/request.ts
2782
2865
  function identityHeaders(config) {
2783
2866
  const headers = { [API_KEY_HEADER]: config.apiKey };
2784
2867
  if (config.sdkVersion) headers[SDK_VERSION_HEADER] = config.sdkVersion;
@@ -2790,8 +2873,8 @@ function identityHeaders(config) {
2790
2873
  function joinUrl(endpoint, path) {
2791
2874
  return `${endpoint.replace(/\/$/, "")}${path}`;
2792
2875
  }
2793
- async function salesRequest(config, method, path, body) {
2794
- const headers = identityHeaders(config);
2876
+ async function apiRequest(config, method, path, body, extraHeaders) {
2877
+ const headers = { ...identityHeaders(config), ...extraHeaders };
2795
2878
  if (body !== void 0) headers["Content-Type"] = "application/json";
2796
2879
  const response = await fetch(joinUrl(config.endpoint, path), {
2797
2880
  method,
@@ -2820,6 +2903,9 @@ async function salesRequest(config, method, path, body) {
2820
2903
  return await response.json();
2821
2904
  }
2822
2905
 
2906
+ // ../tracking-core/src/resources/sales/transport.ts
2907
+ var salesRequest = apiRequest;
2908
+
2823
2909
  // ../tracking-core/src/resources/sales/client.ts
2824
2910
  function fireRecordedConversions(firing, input, recorded, sale, currency) {
2825
2911
  if (!firing) return;
@@ -2851,6 +2937,7 @@ function fireRecordedConversions(firing, input, recorded, sale, currency) {
2851
2937
  }
2852
2938
  }
2853
2939
  function createSalesClient(config) {
2940
+ if (config.firing) registerCapability("conversion_goals_manual");
2854
2941
  async function record(input) {
2855
2942
  const currency = input.currency ?? config.defaultCurrency;
2856
2943
  if (!currency) {
@@ -3150,6 +3237,7 @@ var DEFAULT_CHOICE2 = {
3150
3237
  expiresAt: null
3151
3238
  };
3152
3239
  function useCookiePreferences(options) {
3240
+ registerCapability("consent_controls");
3153
3241
  const [choice, setChoice] = (0, import_react.useState)(DEFAULT_CHOICE2);
3154
3242
  const ttlDays = options?.declineTtlDays;
3155
3243
  (0, import_react.useEffect)(() => {
@@ -3417,6 +3505,8 @@ function AdPlatformTracking({
3417
3505
  }, [trackingConfigKey, standalonePageView]);
3418
3506
  const gtagAll = trackingConfig ? [] : gtagIds ? Object.values(gtagIds).filter((id) => id != null) : gtagId ? [gtagId] : [];
3419
3507
  const metaAll = metaPixelIds ? Object.values(metaPixelIds).filter((id) => id != null) : metaPixelId ? [metaPixelId] : [];
3508
+ if (trackingConfig || gtagAll.length > 0) registerCapability("ad_tags_google");
3509
+ if (metaAll.length > 0) registerCapability("ad_tags_meta");
3420
3510
  const gtagLoaderId = gtagAll[0];
3421
3511
  const gtagInitScript = gtagAll.length === 1 ? createGtagInitScript(gtagAll[0]) : createGtagInitScriptMulti(gtagAll);
3422
3512
  const metaInitScript = metaAll.length === 1 ? createMetaPixelInitScript(metaAll[0]) : createMetaPixelInitScriptMulti(metaAll);
@@ -3465,7 +3555,7 @@ function GoogleAdsTracking(props) {
3465
3555
  var import_navigation3 = require("next/navigation");
3466
3556
 
3467
3557
  // package.json
3468
- var version = "0.20.2";
3558
+ var version = "0.22.0";
3469
3559
 
3470
3560
  // src/factory.tsx
3471
3561
  var import_react5 = require("react");
@@ -3492,6 +3582,7 @@ function usePhoneConfig() {
3492
3582
  };
3493
3583
  }
3494
3584
  function usePhoneField(opts = {}) {
3585
+ registerCapability("phone_fields");
3495
3586
  const cfg = usePhoneConfig();
3496
3587
  const country = opts.country ?? cfg.defaultCountry;
3497
3588
  const display = opts.display ?? cfg.display;
@@ -3578,6 +3669,8 @@ function createTracking(options) {
3578
3669
  conversionConfig,
3579
3670
  trackingConfig
3580
3671
  } = options;
3672
+ registerCapability("base_tracking");
3673
+ if (trackingConfig) registerCapability("conversion_goals_auto");
3581
3674
  if (!apiKey || !endpoint) {
3582
3675
  if (apiKey || endpoint) {
3583
3676
  console.warn(
@@ -3746,43 +3839,490 @@ function createTracking(options) {
3746
3839
  }
3747
3840
  return { TrackingProvider, useTracking };
3748
3841
  }
3842
+
3843
+ // ../tracking-core/src/resources/calendar/transport.ts
3844
+ async function calendarRequest(config, method, path, body) {
3845
+ return apiRequest(config, method, `/calendar${path}`, body);
3846
+ }
3847
+
3848
+ // ../tracking-core/src/resources/calendar/time.ts
3849
+ function resolveBrowserTimeZone() {
3850
+ try {
3851
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
3852
+ } catch {
3853
+ return "UTC";
3854
+ }
3855
+ }
3856
+ function partsInTz(date, timeZone) {
3857
+ const formatter = new Intl.DateTimeFormat("en-US", {
3858
+ timeZone,
3859
+ hour12: false,
3860
+ year: "numeric",
3861
+ month: "2-digit",
3862
+ day: "2-digit",
3863
+ hour: "2-digit",
3864
+ minute: "2-digit",
3865
+ second: "2-digit"
3866
+ });
3867
+ const out = {};
3868
+ for (const part of formatter.formatToParts(date)) {
3869
+ if (part.type !== "literal") out[part.type] = Number(part.value);
3870
+ }
3871
+ if (out.hour === 24) out.hour = 0;
3872
+ return out;
3873
+ }
3874
+ function offsetMinutesAt(date, timeZone) {
3875
+ const p = partsInTz(date, timeZone);
3876
+ const asUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
3877
+ return (asUtc - Math.floor(date.getTime() / 1e3) * 1e3) / 6e4;
3878
+ }
3879
+ function instantFromWallClock(timeZone, year, month, day, hour = 0, minute = 0) {
3880
+ const naive = Date.UTC(year, month - 1, day, hour, minute);
3881
+ const firstGuess = new Date(naive - offsetMinutesAt(new Date(naive), timeZone) * 6e4);
3882
+ const corrected = naive - offsetMinutesAt(firstGuess, timeZone) * 6e4;
3883
+ return new Date(corrected);
3884
+ }
3885
+ function localDateKey(iso, timeZone) {
3886
+ const p = partsInTz(new Date(iso), timeZone);
3887
+ const pad = (n) => String(n).padStart(2, "0");
3888
+ return `${p.year}-${pad(p.month)}-${pad(p.day)}`;
3889
+ }
3890
+ function startOfDayInTz(dateKey, timeZone) {
3891
+ const [year, month, day] = dateKey.split("-").map(Number);
3892
+ return instantFromWallClock(timeZone, year, month, day, 0, 0);
3893
+ }
3894
+ function endOfDayInTz(dateKey, timeZone) {
3895
+ const start = startOfDayInTz(dateKey, timeZone);
3896
+ const p = partsInTz(start, timeZone);
3897
+ return instantFromWallClock(timeZone, p.year, p.month, p.day + 1, 0, 0);
3898
+ }
3899
+ function durationMinutes(startIso, endIso) {
3900
+ return Math.round((new Date(endIso).getTime() - new Date(startIso).getTime()) / 6e4);
3901
+ }
3902
+ function formatSlotRange(startIso, endIso, timeZone, locale) {
3903
+ const opts = { hour: "2-digit", minute: "2-digit", timeZone };
3904
+ const fmt = new Intl.DateTimeFormat(locale, opts);
3905
+ return `${fmt.format(new Date(startIso))} \u2013 ${fmt.format(new Date(endIso))}`;
3906
+ }
3907
+
3908
+ // ../tracking-core/src/resources/calendar/read-client.ts
3909
+ function withBusiness(config, path) {
3910
+ if (!config.businessId) return path;
3911
+ const separator = path.includes("?") ? "&" : "?";
3912
+ return `${path}${separator}business_id=${encodeURIComponent(config.businessId)}`;
3913
+ }
3914
+ function createCalendarReadClient(config) {
3915
+ registerCapability("calendar_read");
3916
+ return {
3917
+ async listCalendars() {
3918
+ return calendarRequest(config, "GET", withBusiness(config, "/calendars"));
3919
+ },
3920
+ async busy(calendarKey, query) {
3921
+ const params = new URLSearchParams({
3922
+ since: query.since,
3923
+ until: query.until
3924
+ });
3925
+ const timeZone = query.timeZone ?? config.defaultTimeZone ?? resolveBrowserTimeZone();
3926
+ params.set("time_zone", timeZone);
3927
+ return calendarRequest(
3928
+ config,
3929
+ "GET",
3930
+ withBusiness(config, `/${encodeURIComponent(calendarKey)}/busy?${params.toString()}`)
3931
+ );
3932
+ }
3933
+ };
3934
+ }
3935
+ function isCalendarError(error, code) {
3936
+ if (!(error instanceof AranovaApiError)) return false;
3937
+ return code === void 0 || error.code === code;
3938
+ }
3939
+
3940
+ // ../tracking-core/src/resources/calendar/schema.ts
3941
+ var import_zod13 = require("zod");
3942
+ var CALENDAR_METADATA_SCHEMA_VERSION = 1;
3943
+ var MAX_ATTENDEES = 20;
3944
+ var MAX_REMINDER_OVERRIDES = 5;
3945
+ var MAX_REMINDER_MINUTES = 40320;
3946
+ var MAX_BOOKING_MINUTES = 1440;
3947
+ var BOOKING_SOURCES = ["website", "phone", "walk_in", "import", "operator"];
3948
+ var BOOKING_STATUSES = ["pending", "confirmed", "cancelled", "no_show"];
3949
+ var GOOGLE_SYNC_STATES = ["queued", "synced", "uncertain", "failed", "drifted"];
3950
+ var CALENDAR_SYNC_STATUSES = [
3951
+ "pending_invite",
3952
+ "awaiting_calendar_selection",
3953
+ "connected",
3954
+ "needs_reconnect",
3955
+ "revoked",
3956
+ "disabled"
3957
+ ];
3958
+ var CALENDAR_ERROR_CODES = [
3959
+ "slot_unavailable",
3960
+ "calendar_not_connected",
3961
+ "calendar_disconnected",
3962
+ "booking_not_found",
3963
+ "outside_booking_window",
3964
+ "rate_limited",
3965
+ "google_error"
3966
+ ];
3967
+ var isoInstant = import_zod13.z.string().datetime({ offset: true });
3968
+ function isKnownTimeZone(timeZone) {
3969
+ try {
3970
+ new Intl.DateTimeFormat(void 0, { timeZone });
3971
+ return true;
3972
+ } catch {
3973
+ return false;
3974
+ }
3975
+ }
3976
+ var ianaTz = import_zod13.z.string().min(1).max(64).regex(/^[A-Za-z0-9_+\-/]+$/).refine(isKnownTimeZone, { message: "unknown IANA time zone" });
3977
+ var bookingAttendeeSchema = import_zod13.z.object({
3978
+ email: import_zod13.z.string().max(320).email(),
3979
+ display_name: import_zod13.z.string().max(200).nullable().optional(),
3980
+ optional: import_zod13.z.boolean().default(false)
3981
+ }).strict();
3982
+ var bookingReminderSchema = import_zod13.z.object({
3983
+ method: import_zod13.z.enum(["email", "popup"]),
3984
+ minutes_before: import_zod13.z.number().int().min(0).max(MAX_REMINDER_MINUTES)
3985
+ }).strict();
3986
+ var bookingRemindersSchema = import_zod13.z.object({
3987
+ use_calendar_defaults: import_zod13.z.boolean().default(true),
3988
+ overrides: import_zod13.z.array(bookingReminderSchema).max(MAX_REMINDER_OVERRIDES).default([])
3989
+ }).strict();
3990
+ var bookingLocationSchema = import_zod13.z.union([
3991
+ import_zod13.z.object({ kind: import_zod13.z.literal("google_meet") }).strict(),
3992
+ import_zod13.z.object({ kind: import_zod13.z.literal("physical"), address: import_zod13.z.string().max(1024) }).strict(),
3993
+ import_zod13.z.object({ kind: import_zod13.z.literal("phone"), phone: import_zod13.z.string().max(64) }).strict(),
3994
+ import_zod13.z.object({ kind: import_zod13.z.literal("custom"), text: import_zod13.z.string().max(1024) }).strict()
3995
+ ]);
3996
+ var bookingLocationNullableSchema = import_zod13.z.union([...bookingLocationSchema.options, import_zod13.z.null()]);
3997
+ var bookingCustomerSchema = import_zod13.z.object({
3998
+ // Required: Google needs an address to send the invite that confirms the
3999
+ // booking, and that invite is the whole confirmation mechanism.
4000
+ email: import_zod13.z.string().max(320).email(),
4001
+ name: import_zod13.z.string().max(200).nullable().optional(),
4002
+ phone: import_zod13.z.string().max(64).nullable().optional(),
4003
+ sms_consent: import_zod13.z.boolean().default(false)
4004
+ }).strict();
4005
+ var jsonScalar = import_zod13.z.union([import_zod13.z.string(), import_zod13.z.number(), import_zod13.z.boolean(), import_zod13.z.null()]);
4006
+ var bookingMetadataSchema = import_zod13.z.object({
4007
+ schema_version: import_zod13.z.number().int().default(CALENDAR_METADATA_SCHEMA_VERSION),
4008
+ source: import_zod13.z.enum(BOOKING_SOURCES).nullable().optional(),
4009
+ service: import_zod13.z.string().max(64).nullable().optional(),
4010
+ visitor_id: import_zod13.z.string().max(64).nullable().optional(),
4011
+ session_id: import_zod13.z.string().max(64).nullable().optional(),
4012
+ gclid: import_zod13.z.string().max(256).nullable().optional(),
4013
+ fbclid: import_zod13.z.string().max(256).nullable().optional(),
4014
+ utm_source: import_zod13.z.string().max(128).nullable().optional(),
4015
+ utm_medium: import_zod13.z.string().max(128).nullable().optional(),
4016
+ utm_campaign: import_zod13.z.string().max(128).nullable().optional(),
4017
+ utm_term: import_zod13.z.string().max(128).nullable().optional(),
4018
+ utm_content: import_zod13.z.string().max(128).nullable().optional(),
4019
+ landing_page: import_zod13.z.string().max(2048).nullable().optional(),
4020
+ referrer: import_zod13.z.string().max(2048).nullable().optional(),
4021
+ notes: import_zod13.z.string().max(2e3).nullable().optional(),
4022
+ custom: import_zod13.z.record(jsonScalar).nullable().optional()
4023
+ }).strict();
4024
+ function refineEndXor(value, ctx) {
4025
+ const hasEnd = value.end_at !== void 0 && value.end_at !== null;
4026
+ const hasDuration = value.duration_minutes !== void 0 && value.duration_minutes !== null;
4027
+ if (hasEnd === hasDuration) {
4028
+ ctx.addIssue({
4029
+ code: import_zod13.z.ZodIssueCode.custom,
4030
+ message: "provide exactly one of end_at or duration_minutes",
4031
+ path: ["end_at"]
4032
+ });
4033
+ return;
4034
+ }
4035
+ if (hasEnd && new Date(value.end_at) <= new Date(value.start_at)) {
4036
+ ctx.addIssue({
4037
+ code: import_zod13.z.ZodIssueCode.custom,
4038
+ message: "end_at must be after start_at",
4039
+ path: ["end_at"]
4040
+ });
4041
+ }
4042
+ }
4043
+ var bookingCreateSchema = import_zod13.z.object({
4044
+ calendar_key: import_zod13.z.string().min(1).max(64),
4045
+ // Free-form: appointment types are the client site's own vocabulary, not
4046
+ // configuration the dashboard holds.
4047
+ appointment_type: import_zod13.z.string().max(64).nullable().optional(),
4048
+ start_at: isoInstant,
4049
+ end_at: isoInstant.nullable().optional(),
4050
+ duration_minutes: import_zod13.z.number().int().positive().max(MAX_BOOKING_MINUTES).nullable().optional(),
4051
+ /** The booker's zone — distinct from the business's and the calendar's. */
4052
+ time_zone: ianaTz,
4053
+ title: import_zod13.z.string().max(300).nullable().optional(),
4054
+ description: import_zod13.z.string().max(8e3).nullable().optional(),
4055
+ location: bookingLocationNullableSchema.optional(),
4056
+ customer: bookingCustomerSchema,
4057
+ attendees: import_zod13.z.array(bookingAttendeeSchema).max(MAX_ATTENDEES).default([]),
4058
+ send_invites: import_zod13.z.boolean().default(true),
4059
+ reminders: bookingRemindersSchema.nullable().optional(),
4060
+ metadata: bookingMetadataSchema.nullable().optional(),
4061
+ idempotency_key: import_zod13.z.string().max(128).nullable().optional(),
4062
+ buffer_before_minutes: import_zod13.z.number().int().min(0).max(MAX_BOOKING_MINUTES).default(0),
4063
+ buffer_after_minutes: import_zod13.z.number().int().min(0).max(MAX_BOOKING_MINUTES).default(0)
4064
+ }).strict().superRefine(refineEndXor);
4065
+ var bookingRescheduleSchema = import_zod13.z.object({
4066
+ start_at: isoInstant,
4067
+ end_at: isoInstant.nullable().optional(),
4068
+ duration_minutes: import_zod13.z.number().int().positive().max(MAX_BOOKING_MINUTES).nullable().optional(),
4069
+ time_zone: ianaTz,
4070
+ notify_attendees: import_zod13.z.boolean().default(true),
4071
+ reason: import_zod13.z.string().max(500).nullable().optional()
4072
+ }).strict().superRefine(refineEndXor);
4073
+ var bookingCancelSchema = import_zod13.z.object({
4074
+ reason: import_zod13.z.string().max(500).nullable().optional(),
4075
+ notify_attendees: import_zod13.z.boolean().default(true)
4076
+ }).strict();
4077
+
4078
+ // ../tracking-core/src/resources/calendar/slots.ts
4079
+ function parseHhMm(value) {
4080
+ const [hour, minute] = value.split(":").map(Number);
4081
+ return [hour || 0, minute || 0];
4082
+ }
4083
+ function dateKeyOf(date, timeZone) {
4084
+ const fmt = new Intl.DateTimeFormat("en-CA", {
4085
+ timeZone,
4086
+ year: "numeric",
4087
+ month: "2-digit",
4088
+ day: "2-digit"
4089
+ });
4090
+ return fmt.format(date);
4091
+ }
4092
+ function weekdayFromDateKey(dateKey) {
4093
+ const [year, month, day] = dateKey.split("-").map(Number);
4094
+ return new Date(Date.UTC(year, month - 1, day, 12)).getUTCDay();
4095
+ }
4096
+ function startOfLocalDay(at, timeZone) {
4097
+ const [year, month, day] = dateKeyOf(at, timeZone).split("-").map(Number);
4098
+ return instantFromWallClock(timeZone, year, month, day, 12, 0);
4099
+ }
4100
+ function overlaps(aStart, aEnd, bStart, bEnd) {
4101
+ return aStart < bEnd && aEnd > bStart;
4102
+ }
4103
+ function computeSlots(options) {
4104
+ const {
4105
+ busy,
4106
+ openingHours,
4107
+ timeZone,
4108
+ durationMinutes: durationMinutes2,
4109
+ granularityMinutes = 15,
4110
+ minNoticeMinutes = 0,
4111
+ bufferBeforeMinutes = 0,
4112
+ bufferAfterMinutes = 0,
4113
+ windowDays = 30,
4114
+ now = /* @__PURE__ */ new Date()
4115
+ } = options;
4116
+ const earliest = now.getTime() + minNoticeMinutes * 6e4;
4117
+ const busyRanges = busy.map((interval) => [
4118
+ new Date(interval.start_at).getTime(),
4119
+ new Date(interval.end_at).getTime()
4120
+ ]);
4121
+ const slots = [];
4122
+ let cursor = startOfLocalDay(now, timeZone);
4123
+ for (let dayOffset = 0; dayOffset < windowDays; dayOffset += 1) {
4124
+ const dateKey = dateKeyOf(cursor, timeZone);
4125
+ const [year, month, day] = dateKey.split("-").map(Number);
4126
+ cursor = instantFromWallClock(timeZone, year, month, day + 1, 12, 0);
4127
+ if (openingHours.closedDates?.includes(dateKey)) continue;
4128
+ const intervals = openingHours.overrides?.[dateKey] ?? openingHours.weekly[weekdayFromDateKey(dateKey)] ?? [];
4129
+ for (const interval of intervals) {
4130
+ const [openHour, openMinute] = parseHhMm(interval.start);
4131
+ const [closeHour, closeMinute] = parseHhMm(interval.end);
4132
+ const opensAt = instantFromWallClock(timeZone, year, month, day, openHour, openMinute);
4133
+ const closesAt = instantFromWallClock(timeZone, year, month, day, closeHour, closeMinute);
4134
+ for (let start = opensAt.getTime(); start + durationMinutes2 * 6e4 <= closesAt.getTime(); start += granularityMinutes * 6e4) {
4135
+ const end = start + durationMinutes2 * 6e4;
4136
+ if (start < earliest) continue;
4137
+ const blockedStart = start - bufferBeforeMinutes * 6e4;
4138
+ const blockedEnd = end + bufferAfterMinutes * 6e4;
4139
+ const taken = busyRanges.some(
4140
+ ([busyStart, busyEnd]) => overlaps(blockedStart, blockedEnd, busyStart, busyEnd)
4141
+ );
4142
+ if (taken) continue;
4143
+ slots.push({
4144
+ start_at: new Date(start).toISOString(),
4145
+ end_at: new Date(end).toISOString()
4146
+ });
4147
+ }
4148
+ }
4149
+ }
4150
+ slots.sort((a, b) => a.start_at.localeCompare(b.start_at));
4151
+ return { slots, nextAvailableAt: slots[0]?.start_at ?? null };
4152
+ }
4153
+ function groupSlotsByDay(slots, timeZone) {
4154
+ const byDay = /* @__PURE__ */ new Map();
4155
+ for (const slot of slots) {
4156
+ const key = dateKeyOf(new Date(slot.start_at), timeZone);
4157
+ const bucket = byDay.get(key);
4158
+ if (bucket) bucket.push(slot);
4159
+ else byDay.set(key, [slot]);
4160
+ }
4161
+ return [...byDay.entries()].map(([date, daySlots]) => ({ date, slots: daySlots })).sort((a, b) => a.date.localeCompare(b.date));
4162
+ }
4163
+
4164
+ // ../tracking-core/src/calendar-react.tsx
4165
+ var import_react6 = require("react");
4166
+ var import_jsx_runtime6 = require("react/jsx-runtime");
4167
+ var _calendarClientContext;
4168
+ function calendarClientContext() {
4169
+ return _calendarClientContext ?? (_calendarClientContext = (0, import_react6.createContext)(null));
4170
+ }
4171
+ function CalendarClientProvider({
4172
+ value,
4173
+ children
4174
+ }) {
4175
+ const Ctx = calendarClientContext();
4176
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Ctx.Provider, { value, children });
4177
+ }
4178
+ function useCalendarClient() {
4179
+ const client = (0, import_react6.useContext)(calendarClientContext());
4180
+ if (!client) {
4181
+ throw new Error(
4182
+ "useCalendarClient requires <CalendarClientProvider>. Wire it via createTracking({ calendar })."
4183
+ );
4184
+ }
4185
+ return client;
4186
+ }
4187
+ function useCalendarBusy(calendarKey, window2) {
4188
+ const client = useCalendarClient();
4189
+ const [busy, setBusy] = (0, import_react6.useState)([]);
4190
+ const [timeZone, setTimeZone] = (0, import_react6.useState)(resolveBrowserTimeZone());
4191
+ const [isLoading, setIsLoading] = (0, import_react6.useState)(true);
4192
+ const [error, setError] = (0, import_react6.useState)(null);
4193
+ const [nonce, setNonce] = (0, import_react6.useState)(0);
4194
+ (0, import_react6.useEffect)(() => {
4195
+ const controller = new AbortController();
4196
+ setIsLoading(true);
4197
+ client.busy(calendarKey, { since: window2.since, until: window2.until }).then((response) => {
4198
+ if (controller.signal.aborted) return;
4199
+ setBusy(response.busy);
4200
+ setTimeZone(response.time_zone);
4201
+ setError(null);
4202
+ }).catch((cause) => {
4203
+ if (controller.signal.aborted) return;
4204
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
4205
+ }).finally(() => {
4206
+ if (!controller.signal.aborted) setIsLoading(false);
4207
+ });
4208
+ return () => controller.abort();
4209
+ }, [client, calendarKey, window2.since, window2.until, nonce]);
4210
+ const refetch = (0, import_react6.useCallback)(() => setNonce((value) => value + 1), []);
4211
+ return { busy, timeZone, isLoading, error, refetch };
4212
+ }
4213
+ function useSlots(busy, timeZone, options) {
4214
+ const zone = options.timeZone ?? timeZone;
4215
+ return (0, import_react6.useMemo)(() => {
4216
+ const { slots, nextAvailableAt } = computeSlots({ ...options, busy, timeZone: zone });
4217
+ return { slots, days: groupSlotsByDay(slots, zone), nextAvailableAt };
4218
+ }, [busy, zone, options]);
4219
+ }
4220
+ function useBookingForm(options = {}) {
4221
+ const endpoint = options.endpoint ?? "/api/aranova/calendar/bookings";
4222
+ const [status, setStatus] = (0, import_react6.useState)("idle");
4223
+ const [error, setError] = (0, import_react6.useState)(null);
4224
+ const [booking, setBooking] = (0, import_react6.useState)(null);
4225
+ const submit = (0, import_react6.useCallback)(
4226
+ async (input) => {
4227
+ setStatus("submitting");
4228
+ setError(null);
4229
+ try {
4230
+ const response = await fetch(endpoint, {
4231
+ method: "POST",
4232
+ headers: { "Content-Type": "application/json" },
4233
+ body: JSON.stringify(input)
4234
+ });
4235
+ const body = await response.json().catch(() => null);
4236
+ if (!response.ok) {
4237
+ const detail = body && typeof body === "object" && typeof body.detail === "string" ? body.detail : "Booking failed";
4238
+ throw new Error(detail);
4239
+ }
4240
+ setBooking(body);
4241
+ setStatus("confirmed");
4242
+ options.onConfirmed?.(body);
4243
+ } catch (cause) {
4244
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
4245
+ setStatus("failed");
4246
+ }
4247
+ },
4248
+ [endpoint, options]
4249
+ );
4250
+ const reset = (0, import_react6.useCallback)(() => {
4251
+ setStatus("idle");
4252
+ setError(null);
4253
+ setBooking(null);
4254
+ }, []);
4255
+ return { status, error, booking, submit, reset };
4256
+ }
3749
4257
  // Annotate the CommonJS export names for ESM import in node:
3750
4258
  0 && (module.exports = {
3751
4259
  AdPlatformTracking,
3752
4260
  AranovaApiError,
4261
+ BOOKING_SOURCES,
4262
+ BOOKING_STATUSES,
4263
+ CALENDAR_ERROR_CODES,
4264
+ CALENDAR_METADATA_SCHEMA_VERSION,
4265
+ CALENDAR_SYNC_STATUSES,
4266
+ CalendarClientProvider,
3753
4267
  ConsentBanner,
3754
4268
  DEFAULT_DECLINE_TTL_DAYS,
3755
4269
  DEFAULT_PHONE_COUNTRY,
3756
4270
  EVENT_SEMANTICS,
4271
+ GOOGLE_SYNC_STATES,
3757
4272
  GoogleAdsTracking,
4273
+ MAX_ATTENDEES,
4274
+ MAX_BOOKING_MINUTES,
4275
+ MAX_REMINDER_MINUTES,
4276
+ MAX_REMINDER_OVERRIDES,
3758
4277
  NAMED_RANGES,
3759
4278
  PhoneField,
3760
4279
  SUPPORTED_CURRENCIES,
3761
4280
  TRACKING_PARAM_KEYS,
3762
4281
  TRACKING_RANGES,
4282
+ bookingAttendeeSchema,
4283
+ bookingCancelSchema,
4284
+ bookingCreateSchema,
4285
+ bookingCustomerSchema,
4286
+ bookingLocationNullableSchema,
4287
+ bookingLocationSchema,
4288
+ bookingMetadataSchema,
4289
+ bookingReminderSchema,
4290
+ bookingRemindersSchema,
4291
+ bookingRescheduleSchema,
3763
4292
  captureTrackingParamsFromLocation,
3764
4293
  clearStashedUserData,
4294
+ computeSlots,
4295
+ createCalendarReadClient,
3765
4296
  createSalesClient,
3766
4297
  createTracking,
3767
4298
  createTrackingClientContext,
3768
4299
  createTrackingEventCreatePayload,
3769
4300
  createTrackingSessionUpsertPayload,
4301
+ durationMinutes,
4302
+ endOfDayInTz,
3770
4303
  fetchServices,
3771
4304
  formatDateInTz,
3772
4305
  formatMoney,
3773
4306
  formatPhone,
3774
4307
  formatPhoneAsTyped,
4308
+ formatSlotRange,
3775
4309
  fromMinor,
3776
4310
  getConsentChoice,
3777
4311
  getConsentState,
3778
4312
  getEventSemantics,
3779
4313
  getTrackingConfigRuntime,
4314
+ groupSlotsByDay,
4315
+ instantFromWallClock,
4316
+ isCalendarError,
4317
+ localDateKey,
4318
+ offsetMinutesAt,
3780
4319
  onConsentChange,
3781
4320
  optIn,
3782
4321
  optOut,
3783
4322
  parsePhone,
3784
4323
  phoneField,
3785
4324
  resetConsent,
4325
+ resolveBrowserTimeZone,
3786
4326
  resolveConversionConfig,
3787
4327
  saleCreateSchema,
3788
4328
  saleItemSchema,
@@ -3790,15 +4330,20 @@ function createTracking(options) {
3790
4330
  saleUpdateSchema,
3791
4331
  salesRequest,
3792
4332
  setConsentState,
4333
+ startOfDayInTz,
3793
4334
  stashUserData,
3794
4335
  toE164,
3795
4336
  toMinor,
4337
+ useBookingForm,
4338
+ useCalendarBusy,
4339
+ useCalendarClient,
3796
4340
  useConsent,
3797
4341
  useConsentState,
3798
4342
  useCookiePreferences,
3799
4343
  useGclid,
3800
4344
  usePhoneConfig,
3801
4345
  usePhoneField,
4346
+ useSlots,
3802
4347
  useTrackingParams
3803
4348
  });
3804
4349
  //# sourceMappingURL=index.js.map