@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/README.md CHANGED
@@ -278,6 +278,76 @@ npx @aranova/tracking-cli gen # reads ARANOVA_TRACKING_SECRET_KEY from
278
278
  See the full guide: [sales-tracking.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/sales-tracking.md)
279
279
  and the CLI reference: [cli.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/cli.md).
280
280
 
281
+ ## Booking handoff (booking integrations)
282
+
283
+ When the business books through an integration (AutoLeap today), the published tracking config
284
+ carries `integrations.autoleap.booking`. After a lead form's successful submit (and after
285
+ `form_submit` fires), offer the optional "Pick your time" step:
286
+
287
+ ```tsx
288
+ import { BookingHandoffLink } from "@aranova/tracking-react";
289
+ import { ARANOVA_TRACKING_CONFIG } from "@/aranova-services";
290
+
291
+ <BookingHandoffLink
292
+ trackingConfig={ARANOVA_TRACKING_CONFIG}
293
+ fallbackUrl="https://myalp.io/…" // baked last resort when no config has loaded
294
+ prefill={{ firstName, lastName, phone, email }}
295
+ >
296
+ Pick your time
297
+ </BookingHandoffLink>;
298
+ ```
299
+
300
+ - The link is the shop's own booking page from the live config, else `fallbackUrl`; it renders
301
+ nothing unless it is `https://myalp.io/…`. The phone goes out as digits.
302
+ - It shows even when `enabled` is false: the customer always has a way to book.
303
+ - `useBookingIntegration(trackingConfig)` returns the live provider (or null before a config
304
+ loads), from the shared config runtime (no extra fetch): `enabled`, `fallbackUrl`,
305
+ `acceptsBookings`, `timezone`, `configVersion`, `rules` and `options`.
306
+ `getBookingIntegration(integrations)` and `buildBookingHandoffUrl(url, prefill)` are the
307
+ React-free equivalents.
308
+ - Reports the `booking_handoff` capability.
309
+
310
+ ## Website bookings (booking integrations)
311
+
312
+ When `acceptsBookings` is true the site can book straight into the shop's system through Aranova's
313
+ API. The SDK is headless; build the form in the site's own design system:
314
+
315
+ ```tsx
316
+ import { useBookingIntegration, useBookingSlots, useBookingSubmit } from "@aranova/tracking-react";
317
+
318
+ const booking = useBookingIntegration(ARANOVA_TRACKING_CONFIG);
319
+ const days = useBookingSlots(ARANOVA_TRACKING_CONFIG); // [{ date, weekday, morning, afternoon }]
320
+ const { status, outcome, submit } = useBookingSubmit(conversions.bookings, {
321
+ formSubmit: { tracking, formId: "booking-form" },
322
+ });
323
+
324
+ await submit({
325
+ startDate: "2026-10-06",
326
+ startHour: 9,
327
+ firstName,
328
+ lastName,
329
+ phone,
330
+ email,
331
+ vehicle: { year: 2019, make: "Toyota", model: "RAV4" },
332
+ dropOff: "dropOff", // only when booking.rules.dropOffOptions is non-empty
333
+ services: ["brakes"], // keys from booking.options
334
+ notes,
335
+ marketingSmsConsent, // the customer's own unchecked opt-in; never default it on
336
+ });
337
+ ```
338
+
339
+ - `conversions` is the site's one `createSalesClient({ apiKey, endpoint, firing: getTrackingConfigRuntime(...) })`;
340
+ `.bookings` reads the rules from that runtime.
341
+ - `outcome.status`: `created` (the shop texts and emails the customer itself; send nothing of
342
+ your own), `pending` (same success; still being sent, the owner is alerted if it fails),
343
+ `failed` (success copy plus `outcome.fallbackUrl`, the prefilled shop page), `invalid`
344
+ (`outcome.message`, nothing sent), `stale` (the times changed; the config is refetched).
345
+ - `useBookingSlots` / `bookableSlots(rules, { timeZone })` offer exactly the portal's start hours,
346
+ judged by the shop's date. `useBookingSubmit` reuses one attempt key across a double-click or
347
+ a retry, and sends the visit's `form_submit` once: don't send your own for this form.
348
+ - Ask for the last name separately, and a mobile number (the shop's confirmation text goes to it).
349
+ - Reports the `booking_integration` capability.
350
+
281
351
  ## On-site conversion firing
282
352
 
283
353
  Run `tracking-cli gen` once to emit `ARANOVA_TRACKING_CONFIG`, then use that
@@ -1,3 +1,5 @@
1
+ import { a as FormSubmitMetadata } from './form-submit-BLb28aCk.js';
2
+
1
3
  interface ServiceFiring {
2
4
  send_to: string;
3
5
  /** Action's configured default value (cents); the SDK fires it only when a sale has no amount. */
@@ -41,6 +43,51 @@ interface ConversionConfig {
41
43
  goals: ConversionGoal[];
42
44
  /** Sibling hostnames of this site (roster minus self); the cross-site linker's targets. */
43
45
  linked_hostnames?: string[];
46
+ /** Third-party integrations the site renders UI for; a missing provider means none. */
47
+ integrations?: ConversionIntegrations;
48
+ }
49
+ /** Bookable start hours on one weekday, shop wall time (8 = 08:00); both empty = closed. */
50
+ interface BookingDayWindows {
51
+ morning: number[];
52
+ afternoon: number[];
53
+ }
54
+ interface BookingRulesConfig {
55
+ min_days_ahead: number;
56
+ max_days_ahead: number;
57
+ /** Keyed `monday` … `sunday`. */
58
+ hours: Record<string, BookingDayWindows>;
59
+ /** `YYYY-MM-DD` shop dates the portal refuses although the weekday is open. */
60
+ closed_dates: string[];
61
+ /** `YYYY-MM-DD` shop dates whose start hours replace their weekday's; never also closed. */
62
+ special_hours: Record<string, BookingDayWindows>;
63
+ vehicle_required: boolean;
64
+ /** Empty when the shop hides the drop-off question. */
65
+ drop_off_options: string[];
66
+ }
67
+ /** One service choice on the site's booking form; the shop system's ids stay server-side. */
68
+ interface BookingOptionConfig {
69
+ /** What the site posts in `services`. */
70
+ key: string;
71
+ label: string;
72
+ }
73
+ /** `integrations.autoleap.booking` — never carries a secret. */
74
+ interface AutoLeapBookingConfig {
75
+ /** False: show only `fallback_url`. The site never blocks the customer on it. */
76
+ enabled: boolean;
77
+ /** The shop's own booking page; always present. */
78
+ fallback_url: string;
79
+ timezone: string;
80
+ /** Snapshot the rules came from; null before the first one. */
81
+ config_version: number | null;
82
+ /** `enabled` and at least one option mapped: the site may post a booking. */
83
+ accepts_bookings: boolean;
84
+ rules: BookingRulesConfig | null;
85
+ options: BookingOptionConfig[];
86
+ }
87
+ interface ConversionIntegrations {
88
+ autoleap?: {
89
+ booking: AutoLeapBookingConfig;
90
+ };
44
91
  }
45
92
  interface ConversionConfigStore {
46
93
  /** Firing config for a goal/service key, or null when it doesn't fire on-site. */
@@ -190,6 +237,11 @@ declare class TrackingConfigRuntime {
190
237
  listGoals(): ConversionGoal[];
191
238
  /** Sibling hostnames for the linker — read from the cached object even while unconfirmed. */
192
239
  linkedHostnames(): readonly string[];
240
+ /**
241
+ * Integration blocks, from the cached object even while unconfirmed: the booking fallback
242
+ * link is safe to show stale. Same reference until the next adopt, for `useSyncExternalStore`.
243
+ */
244
+ integrations(): ConversionIntegrations | null;
193
245
  private revalidateNow;
194
246
  private expireAuthority;
195
247
  private confirm;
@@ -199,4 +251,95 @@ declare class TrackingConfigRuntime {
199
251
  }
200
252
  declare function getTrackingConfigRuntime(ref: TrackingConfigReference, fetchImpl?: typeof fetch): TrackingConfigRuntime;
201
253
 
202
- export { type ConversionConfig as C, type TrackingConfigReference as T, type ConversionConfigStore as a, resolveTrackingConfigUrl as b, TrackingConfigRuntime as c, getTrackingConfigRuntime as g, resolveConversionConfig as r };
254
+ /** A UUID v4 from Web Crypto, with a `getRandomValues` (then `Math.random`) fallback. */
255
+ declare function uuidV4(): string;
256
+
257
+ declare const BOOKING_DROP_OFF_OPTIONS: readonly ["waitForIt", "dropOff"];
258
+ type BookingDropOff = (typeof BOOKING_DROP_OFF_OPTIONS)[number];
259
+ /** What the booking form collected. Dates and hours are the shop's wall time. */
260
+ interface BookingInput {
261
+ /** `YYYY-MM-DD`, one of `bookableSlots(...)`'s dates. */
262
+ startDate: string;
263
+ /** A start hour offered on that date (8 = 08:00). */
264
+ startHour: number;
265
+ firstName: string;
266
+ /** Required: the shop's system needs it, and a single-word name has none to split off. */
267
+ lastName: string;
268
+ /** Mobile number; the shop texts its confirmation to it. */
269
+ phone: string;
270
+ email?: string | null;
271
+ vehicle?: {
272
+ year: number;
273
+ make: string;
274
+ model: string;
275
+ } | null;
276
+ /** Ask only when the rules list drop-off options. */
277
+ dropOff?: BookingDropOff | null;
278
+ /** Option keys from `useBookingIntegration(...).options`. */
279
+ services: string[];
280
+ notes?: string | null;
281
+ /**
282
+ * The customer's own opt-in to marketing texts. Never default it on (CASL). Service texts
283
+ * about this booking need no opt-in and are always on.
284
+ */
285
+ marketingSmsConsent?: boolean;
286
+ }
287
+ /** The form's `form_submit`, sent once per booking attempt and tied to this visit. */
288
+ interface BookingFormSubmit {
289
+ /** The site's tracking client (`useTracking()`): the booking takes the visit it reports. */
290
+ tracking: {
291
+ trackEvent(eventType: "form_submit", metadata: FormSubmitMetadata): void;
292
+ getSessionId(): string;
293
+ };
294
+ /** The `<form id>`, so `form_start` and `form_submit` line up. */
295
+ formId: string;
296
+ }
297
+ type BookingOutcome =
298
+ /** The shop has the request; it texts and emails the customer itself. */
299
+ {
300
+ status: "created";
301
+ id: string;
302
+ requestCode: string | null;
303
+ }
304
+ /** Received and still being sent; the shop confirms. Show the same success. */
305
+ | {
306
+ status: "pending";
307
+ id: string;
308
+ }
309
+ /**
310
+ * Not created. Still a success for the customer: show the prefilled shop booking link.
311
+ * `id` is null when the booking never reached Aranova (offline, rate-limited, booking off).
312
+ */
313
+ | {
314
+ status: "failed";
315
+ id: string | null;
316
+ fallbackUrl: string | null;
317
+ }
318
+ /** The booking breaks the shop's rules; `message` is customer-safe. Nothing was sent. */
319
+ | {
320
+ status: "invalid";
321
+ message: string;
322
+ reasons: string[];
323
+ }
324
+ /** The shop's times changed since the form loaded; the config is being refetched. */
325
+ | {
326
+ status: "stale";
327
+ message: string;
328
+ };
329
+ interface SalesBookingsClient {
330
+ /**
331
+ * Send one booking. Never rejects for an API outcome: every answer is a {@link BookingOutcome}.
332
+ * Pass the same `attemptKey` (a UUID, `newBookingAttemptKey()`) on a retry of the same submit,
333
+ * so a double-click or a network retry can never book twice.
334
+ */
335
+ submit(input: BookingInput, options: {
336
+ attemptKey: string;
337
+ formSubmit?: BookingFormSubmit;
338
+ }): Promise<BookingOutcome>;
339
+ /** A booking's current status by the id `submit` returned. */
340
+ status(id: string): Promise<BookingOutcome>;
341
+ }
342
+ /** A fresh per-submit key. Keep it for retries of the same submit; mint a new one after. */
343
+ declare const newBookingAttemptKey: typeof uuidV4;
344
+
345
+ export { type BookingDropOff as B, type ConversionConfig as C, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingFormSubmit as a, type BookingInput as b, type BookingOutcome as c, type ConversionConfigStore as d, resolveTrackingConfigUrl as e, type BookingDayWindows as f, getTrackingConfigRuntime as g, type ConversionIntegrations as h, TrackingConfigRuntime as i, newBookingAttemptKey as n, resolveConversionConfig as r };
@@ -1,3 +1,5 @@
1
+ import { a as FormSubmitMetadata } from './form-submit-BLb28aCk.mjs';
2
+
1
3
  interface ServiceFiring {
2
4
  send_to: string;
3
5
  /** Action's configured default value (cents); the SDK fires it only when a sale has no amount. */
@@ -41,6 +43,51 @@ interface ConversionConfig {
41
43
  goals: ConversionGoal[];
42
44
  /** Sibling hostnames of this site (roster minus self); the cross-site linker's targets. */
43
45
  linked_hostnames?: string[];
46
+ /** Third-party integrations the site renders UI for; a missing provider means none. */
47
+ integrations?: ConversionIntegrations;
48
+ }
49
+ /** Bookable start hours on one weekday, shop wall time (8 = 08:00); both empty = closed. */
50
+ interface BookingDayWindows {
51
+ morning: number[];
52
+ afternoon: number[];
53
+ }
54
+ interface BookingRulesConfig {
55
+ min_days_ahead: number;
56
+ max_days_ahead: number;
57
+ /** Keyed `monday` … `sunday`. */
58
+ hours: Record<string, BookingDayWindows>;
59
+ /** `YYYY-MM-DD` shop dates the portal refuses although the weekday is open. */
60
+ closed_dates: string[];
61
+ /** `YYYY-MM-DD` shop dates whose start hours replace their weekday's; never also closed. */
62
+ special_hours: Record<string, BookingDayWindows>;
63
+ vehicle_required: boolean;
64
+ /** Empty when the shop hides the drop-off question. */
65
+ drop_off_options: string[];
66
+ }
67
+ /** One service choice on the site's booking form; the shop system's ids stay server-side. */
68
+ interface BookingOptionConfig {
69
+ /** What the site posts in `services`. */
70
+ key: string;
71
+ label: string;
72
+ }
73
+ /** `integrations.autoleap.booking` — never carries a secret. */
74
+ interface AutoLeapBookingConfig {
75
+ /** False: show only `fallback_url`. The site never blocks the customer on it. */
76
+ enabled: boolean;
77
+ /** The shop's own booking page; always present. */
78
+ fallback_url: string;
79
+ timezone: string;
80
+ /** Snapshot the rules came from; null before the first one. */
81
+ config_version: number | null;
82
+ /** `enabled` and at least one option mapped: the site may post a booking. */
83
+ accepts_bookings: boolean;
84
+ rules: BookingRulesConfig | null;
85
+ options: BookingOptionConfig[];
86
+ }
87
+ interface ConversionIntegrations {
88
+ autoleap?: {
89
+ booking: AutoLeapBookingConfig;
90
+ };
44
91
  }
45
92
  interface ConversionConfigStore {
46
93
  /** Firing config for a goal/service key, or null when it doesn't fire on-site. */
@@ -190,6 +237,11 @@ declare class TrackingConfigRuntime {
190
237
  listGoals(): ConversionGoal[];
191
238
  /** Sibling hostnames for the linker — read from the cached object even while unconfirmed. */
192
239
  linkedHostnames(): readonly string[];
240
+ /**
241
+ * Integration blocks, from the cached object even while unconfirmed: the booking fallback
242
+ * link is safe to show stale. Same reference until the next adopt, for `useSyncExternalStore`.
243
+ */
244
+ integrations(): ConversionIntegrations | null;
193
245
  private revalidateNow;
194
246
  private expireAuthority;
195
247
  private confirm;
@@ -199,4 +251,95 @@ declare class TrackingConfigRuntime {
199
251
  }
200
252
  declare function getTrackingConfigRuntime(ref: TrackingConfigReference, fetchImpl?: typeof fetch): TrackingConfigRuntime;
201
253
 
202
- export { type ConversionConfig as C, type TrackingConfigReference as T, type ConversionConfigStore as a, resolveTrackingConfigUrl as b, TrackingConfigRuntime as c, getTrackingConfigRuntime as g, resolveConversionConfig as r };
254
+ /** A UUID v4 from Web Crypto, with a `getRandomValues` (then `Math.random`) fallback. */
255
+ declare function uuidV4(): string;
256
+
257
+ declare const BOOKING_DROP_OFF_OPTIONS: readonly ["waitForIt", "dropOff"];
258
+ type BookingDropOff = (typeof BOOKING_DROP_OFF_OPTIONS)[number];
259
+ /** What the booking form collected. Dates and hours are the shop's wall time. */
260
+ interface BookingInput {
261
+ /** `YYYY-MM-DD`, one of `bookableSlots(...)`'s dates. */
262
+ startDate: string;
263
+ /** A start hour offered on that date (8 = 08:00). */
264
+ startHour: number;
265
+ firstName: string;
266
+ /** Required: the shop's system needs it, and a single-word name has none to split off. */
267
+ lastName: string;
268
+ /** Mobile number; the shop texts its confirmation to it. */
269
+ phone: string;
270
+ email?: string | null;
271
+ vehicle?: {
272
+ year: number;
273
+ make: string;
274
+ model: string;
275
+ } | null;
276
+ /** Ask only when the rules list drop-off options. */
277
+ dropOff?: BookingDropOff | null;
278
+ /** Option keys from `useBookingIntegration(...).options`. */
279
+ services: string[];
280
+ notes?: string | null;
281
+ /**
282
+ * The customer's own opt-in to marketing texts. Never default it on (CASL). Service texts
283
+ * about this booking need no opt-in and are always on.
284
+ */
285
+ marketingSmsConsent?: boolean;
286
+ }
287
+ /** The form's `form_submit`, sent once per booking attempt and tied to this visit. */
288
+ interface BookingFormSubmit {
289
+ /** The site's tracking client (`useTracking()`): the booking takes the visit it reports. */
290
+ tracking: {
291
+ trackEvent(eventType: "form_submit", metadata: FormSubmitMetadata): void;
292
+ getSessionId(): string;
293
+ };
294
+ /** The `<form id>`, so `form_start` and `form_submit` line up. */
295
+ formId: string;
296
+ }
297
+ type BookingOutcome =
298
+ /** The shop has the request; it texts and emails the customer itself. */
299
+ {
300
+ status: "created";
301
+ id: string;
302
+ requestCode: string | null;
303
+ }
304
+ /** Received and still being sent; the shop confirms. Show the same success. */
305
+ | {
306
+ status: "pending";
307
+ id: string;
308
+ }
309
+ /**
310
+ * Not created. Still a success for the customer: show the prefilled shop booking link.
311
+ * `id` is null when the booking never reached Aranova (offline, rate-limited, booking off).
312
+ */
313
+ | {
314
+ status: "failed";
315
+ id: string | null;
316
+ fallbackUrl: string | null;
317
+ }
318
+ /** The booking breaks the shop's rules; `message` is customer-safe. Nothing was sent. */
319
+ | {
320
+ status: "invalid";
321
+ message: string;
322
+ reasons: string[];
323
+ }
324
+ /** The shop's times changed since the form loaded; the config is being refetched. */
325
+ | {
326
+ status: "stale";
327
+ message: string;
328
+ };
329
+ interface SalesBookingsClient {
330
+ /**
331
+ * Send one booking. Never rejects for an API outcome: every answer is a {@link BookingOutcome}.
332
+ * Pass the same `attemptKey` (a UUID, `newBookingAttemptKey()`) on a retry of the same submit,
333
+ * so a double-click or a network retry can never book twice.
334
+ */
335
+ submit(input: BookingInput, options: {
336
+ attemptKey: string;
337
+ formSubmit?: BookingFormSubmit;
338
+ }): Promise<BookingOutcome>;
339
+ /** A booking's current status by the id `submit` returned. */
340
+ status(id: string): Promise<BookingOutcome>;
341
+ }
342
+ /** A fresh per-submit key. Keep it for retries of the same submit; mint a new one after. */
343
+ declare const newBookingAttemptKey: typeof uuidV4;
344
+
345
+ export { type BookingDropOff as B, type ConversionConfig as C, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingFormSubmit as a, type BookingInput as b, type BookingOutcome as c, type ConversionConfigStore as d, resolveTrackingConfigUrl as e, type BookingDayWindows as f, getTrackingConfigRuntime as g, type ConversionIntegrations as h, TrackingConfigRuntime as i, newBookingAttemptKey as n, resolveConversionConfig as r };
package/dist/client.d.mts CHANGED
@@ -1,11 +1,11 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent } from 'react';
3
- import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, k as PhoneConfig, P as ParsedPhone, l as PhoneDisplayFormat } from './phone-utils-qLfVpQI7.mjs';
4
- import { T as TrackingConfigReference, C as ConversionConfig } from './tracking-config-runtime-C6H7ofmo.mjs';
5
- import { L as TriggerRegistryConfig, O as TypedTrackingClient } from './ingest-typed-D1sYF0bA.mjs';
2
+ import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
+ import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, j as PhoneConfig, P as ParsedPhone, k as PhoneDisplayFormat } from './phone-utils-CM8eUPz8.mjs';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, c as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit } from './bookings-VsQdopTt.mjs';
5
+ import { W as TriggerRegistryConfig, Y as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-BCUv93ax.mjs';
6
6
  import { CountryCode } from 'libphonenumber-js';
7
+ import './form-submit-BLb28aCk.mjs';
7
8
  import 'zod/mini';
8
- import 'src';
9
9
  import 'zod/v4/core';
10
10
 
11
11
  /**
@@ -370,4 +370,54 @@ interface PhoneFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "t
370
370
  */
371
371
  declare const PhoneField: react.ForwardRefExoticComponent<PhoneFieldProps & react.RefAttributes<HTMLInputElement>>;
372
372
 
373
- export { AdPlatformTracking, type AdPlatformTrackingProps, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };
373
+ /**
374
+ * The site's booking provider from the live runtime config, or null before one loads.
375
+ *
376
+ * Pass the same `ARANOVA_TRACKING_CONFIG` reference given to `createTracking`; the config
377
+ * runtime is shared, so this adds no fetch.
378
+ */
379
+ declare function useBookingIntegration(trackingConfig: TrackingConfigReference | undefined): BookingIntegration | null;
380
+ interface BookingHandoffOptions {
381
+ /** Baked copy of the booking link, used when no runtime config has loaded. */
382
+ fallbackUrl?: string;
383
+ prefill?: BookingHandoffPrefill;
384
+ }
385
+ /**
386
+ * The prefilled handoff URL, or null when neither the runtime config nor `fallbackUrl` names a
387
+ * usable link. For sites that render the link with their own button component.
388
+ */
389
+ declare function useBookingHandoffUrl(trackingConfig: TrackingConfigReference | undefined, { fallbackUrl, prefill }?: BookingHandoffOptions): string | null;
390
+ interface BookingHandoffLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">, BookingHandoffOptions {
391
+ trackingConfig?: TrackingConfigReference;
392
+ children: ReactNode;
393
+ }
394
+ /**
395
+ * The optional "pick your time" step after a form submit: the shop's booking page, prefilled.
396
+ * Renders nothing when neither the runtime config nor `fallbackUrl` names a usable link.
397
+ */
398
+ declare function BookingHandoffLink({ trackingConfig, fallbackUrl, prefill, children, target, rel, ...anchorProps }: BookingHandoffLinkProps): ReactNode;
399
+ /**
400
+ * Every bookable day and its start hours, split morning / afternoon, from the live rules.
401
+ * Empty until the config loads or when the shop takes no online bookings. Recomputed each
402
+ * render (cheap), so a tab left open past midnight never offers yesterday's notice window.
403
+ */
404
+ declare function useBookingSlots(trackingConfig: TrackingConfigReference | undefined): BookableDay[];
405
+ type BookingSubmitStatus = "idle" | "submitting" | BookingOutcome["status"];
406
+ interface BookingSubmitState {
407
+ status: BookingSubmitStatus;
408
+ outcome: BookingOutcome | null;
409
+ /** Send the booking. A retry of an unanswered or refused submit reuses its attempt key. */
410
+ submit(input: BookingInput): Promise<BookingOutcome>;
411
+ /** Back to `idle` with a fresh attempt key, e.g. "book another car". */
412
+ reset(): void;
413
+ }
414
+ /**
415
+ * Submit state for a booking form over `salesClient.bookings`. Owns the parts that are easy to
416
+ * get wrong: one attempt key per submit (a double-click never books twice), the visit's
417
+ * `form_submit`, the stale-config refetch, and the fallback link on failure.
418
+ */
419
+ declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
420
+ formSubmit?: BookingFormSubmit;
421
+ }): BookingSubmitState;
422
+
423
+ export { AdPlatformTracking, type AdPlatformTrackingProps, BookingHandoffLink, type BookingHandoffLinkProps, type BookingHandoffOptions, type BookingSubmitState, type BookingSubmitStatus, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useBookingHandoffUrl, useBookingIntegration, useBookingSlots, useBookingSubmit, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };
package/dist/client.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent } from 'react';
3
- import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, k as PhoneConfig, P as ParsedPhone, l as PhoneDisplayFormat } from './phone-utils-qLfVpQI7.js';
4
- import { T as TrackingConfigReference, C as ConversionConfig } from './tracking-config-runtime-C6H7ofmo.js';
5
- import { L as TriggerRegistryConfig, O as TypedTrackingClient } from './ingest-typed-QKX9WGr3.js';
2
+ import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
+ import { G as GtagEnvironmentMap, M as MetaPixelEnvironmentMap, h as ConsentState, f as ConsentChoiceState, g as ConsentSource, T as TrackingParams, b as TrackingEnvironment, j as PhoneConfig, P as ParsedPhone, k as PhoneDisplayFormat } from './phone-utils-l8ey3Zdm.js';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, c as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit } from './bookings-CqdjLMkv.js';
5
+ import { W as TriggerRegistryConfig, Y as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-DGqzbteM.js';
6
6
  import { CountryCode } from 'libphonenumber-js';
7
+ import './form-submit-BLb28aCk.js';
7
8
  import 'zod/mini';
8
- import 'src';
9
9
  import 'zod/v4/core';
10
10
 
11
11
  /**
@@ -370,4 +370,54 @@ interface PhoneFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "t
370
370
  */
371
371
  declare const PhoneField: react.ForwardRefExoticComponent<PhoneFieldProps & react.RefAttributes<HTMLInputElement>>;
372
372
 
373
- export { AdPlatformTracking, type AdPlatformTrackingProps, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };
373
+ /**
374
+ * The site's booking provider from the live runtime config, or null before one loads.
375
+ *
376
+ * Pass the same `ARANOVA_TRACKING_CONFIG` reference given to `createTracking`; the config
377
+ * runtime is shared, so this adds no fetch.
378
+ */
379
+ declare function useBookingIntegration(trackingConfig: TrackingConfigReference | undefined): BookingIntegration | null;
380
+ interface BookingHandoffOptions {
381
+ /** Baked copy of the booking link, used when no runtime config has loaded. */
382
+ fallbackUrl?: string;
383
+ prefill?: BookingHandoffPrefill;
384
+ }
385
+ /**
386
+ * The prefilled handoff URL, or null when neither the runtime config nor `fallbackUrl` names a
387
+ * usable link. For sites that render the link with their own button component.
388
+ */
389
+ declare function useBookingHandoffUrl(trackingConfig: TrackingConfigReference | undefined, { fallbackUrl, prefill }?: BookingHandoffOptions): string | null;
390
+ interface BookingHandoffLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">, BookingHandoffOptions {
391
+ trackingConfig?: TrackingConfigReference;
392
+ children: ReactNode;
393
+ }
394
+ /**
395
+ * The optional "pick your time" step after a form submit: the shop's booking page, prefilled.
396
+ * Renders nothing when neither the runtime config nor `fallbackUrl` names a usable link.
397
+ */
398
+ declare function BookingHandoffLink({ trackingConfig, fallbackUrl, prefill, children, target, rel, ...anchorProps }: BookingHandoffLinkProps): ReactNode;
399
+ /**
400
+ * Every bookable day and its start hours, split morning / afternoon, from the live rules.
401
+ * Empty until the config loads or when the shop takes no online bookings. Recomputed each
402
+ * render (cheap), so a tab left open past midnight never offers yesterday's notice window.
403
+ */
404
+ declare function useBookingSlots(trackingConfig: TrackingConfigReference | undefined): BookableDay[];
405
+ type BookingSubmitStatus = "idle" | "submitting" | BookingOutcome["status"];
406
+ interface BookingSubmitState {
407
+ status: BookingSubmitStatus;
408
+ outcome: BookingOutcome | null;
409
+ /** Send the booking. A retry of an unanswered or refused submit reuses its attempt key. */
410
+ submit(input: BookingInput): Promise<BookingOutcome>;
411
+ /** Back to `idle` with a fresh attempt key, e.g. "book another car". */
412
+ reset(): void;
413
+ }
414
+ /**
415
+ * Submit state for a booking form over `salesClient.bookings`. Owns the parts that are easy to
416
+ * get wrong: one attempt key per submit (a double-click never books twice), the visit's
417
+ * `form_submit`, the stale-config refetch, and the fallback link on failure.
418
+ */
419
+ declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
420
+ formSubmit?: BookingFormSubmit;
421
+ }): BookingSubmitState;
422
+
423
+ export { AdPlatformTracking, type AdPlatformTrackingProps, BookingHandoffLink, type BookingHandoffLinkProps, type BookingHandoffOptions, type BookingSubmitState, type BookingSubmitStatus, ConsentBanner, type ConsentBannerProps, type CreateTrackingOptions, type CreateTrackingResult, GoogleAdsTracking, type GoogleAdsTrackingProps, PhoneField, type PhoneFieldApi, type PhoneFieldProps, type PhoneInputProps, type TrackingProviderProps, type UseConsentResult, type UseCookiePreferencesOptions, type UseCookiePreferencesResult, type UsePhoneFieldOptions, createTracking, useBookingHandoffUrl, useBookingIntegration, useBookingSlots, useBookingSubmit, useConsent, useConsentState, useCookiePreferences, useGclid, usePhoneConfig, usePhoneField, useTrackingParams };