@aranova/tracking-react 1.4.1 → 1.6.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
@@ -318,7 +318,14 @@ import { useBookingIntegration, useBookingSlots, useBookingSubmit } from "@arano
318
318
  const booking = useBookingIntegration(ARANOVA_TRACKING_CONFIG);
319
319
  const days = useBookingSlots(ARANOVA_TRACKING_CONFIG); // [{ date, weekday, morning, afternoon }]
320
320
  const { status, outcome, submit } = useBookingSubmit(conversions.bookings, {
321
- formSubmit: { tracking, formId: "booking-form" },
321
+ formSubmit: {
322
+ tracking,
323
+ formId: "booking-form",
324
+ // Your own questions, for our reporting only; never sent to the shop.
325
+ extraFields: [
326
+ { name: "heardVia", type: "select", label: "How did you hear about us?", value: heardVia },
327
+ ],
328
+ },
322
329
  });
323
330
 
324
331
  await submit({
@@ -339,7 +346,8 @@ await submit({
339
346
  - `conversions` is the site's one `createSalesClient({ apiKey, endpoint, firing: getTrackingConfigRuntime(...) })`;
340
347
  `.bookings` reads the rules from that runtime.
341
348
  - `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),
349
+ your own), `pending` (same success; still being sent, the owner is alerted if it fails;
350
+ `status` turns `pending` as soon as the server says so, while the final answer is polled),
343
351
  `failed` (success copy plus `outcome.fallbackUrl`, the prefilled shop page), `invalid`
344
352
  (`outcome.message`, nothing sent), `stale` (the times changed; the config is refetched).
345
353
  - `outcome.mode` (and `booking.mode` before submitting): `live` for every real customer;
@@ -349,9 +357,14 @@ await submit({
349
357
  outcome (`created`, `rejected`, `unknown`, `failed`) to exercise each screen. The production
350
358
  API ignores it. Test locally against the local API **and** the local CDN; the SDK warns in the
351
359
  console when the endpoint is localhost but the config comes from production.
360
+ - `booking.options` arrive grouped by the shop's service category: each has `group` (the heading,
361
+ or `null`) and an optional `description`. `groupBookingOptions(booking.options)` returns
362
+ `[{ group, options }]` in order, so render one heading per group with its choices under it.
352
363
  - `useBookingSlots` / `bookableSlots(rules, { timeZone })` offer exactly the portal's start hours,
353
364
  judged by the shop's date. `useBookingSubmit` reuses one attempt key across a double-click or
354
- a retry, and sends the visit's `form_submit` once: don't send your own for this form.
365
+ a retry, and sends the visit's `form_submit` once: don't send your own for this form. Add the
366
+ form's own questions with `formSubmit.extraFields`; a name the booking already reports
367
+ (`phone`, `email`, ...) is dropped.
355
368
  - Ask for the last name separately, and a mobile number (the shop's confirmation text goes to it).
356
369
  - Reports the `booking_integration` capability.
357
370
 
@@ -69,6 +69,10 @@ interface BookingOptionConfig {
69
69
  /** What the site posts in `services`. */
70
70
  key: string;
71
71
  label: string;
72
+ /** The shop's service category; options arrive grouped, each category in one run. */
73
+ group: string | null;
74
+ /** Optional helper text under the label. */
75
+ description: string | null;
72
76
  }
73
77
  /** Where a booking went; decided by the API. A real customer always gets `live`. */
74
78
  declare const BOOKING_MODES: readonly ["live", "test", "simulated"];
@@ -305,7 +309,15 @@ interface BookingFormSubmit {
305
309
  };
306
310
  /** The `<form id>`, so `form_start` and `form_submit` line up. */
307
311
  formId: string;
312
+ /**
313
+ * The site's own questions (e.g. "How did you hear about us?"), appended to this
314
+ * `form_submit` for our reporting; never sent to the shop. A name the booking already
315
+ * reports (`phone`, `email`, ...) is dropped, so lead matching cannot be overridden.
316
+ */
317
+ extraFields?: FormSubmitField[];
308
318
  }
319
+ /** One field of a `form_submit`. */
320
+ type FormSubmitField = NonNullable<FormSubmitMetadata["form"]["fields"]>[number];
309
321
  type BookingOutcome =
310
322
  /** The shop has the request; it texts and emails the customer itself. */
311
323
  {
@@ -349,6 +361,13 @@ interface BookingSubmitOptions {
349
361
  * fallback screens. The production API ignores it, so it can never change a real booking.
350
362
  */
351
363
  simulate?: BookingSimulation;
364
+ /**
365
+ * Called once when the server answers `pending`, before `submit` polls for the final answer
366
+ * (up to ~30 s): show the pending success right away instead of a spinner.
367
+ */
368
+ onPending?: (outcome: Extract<BookingOutcome, {
369
+ status: "pending";
370
+ }>) => void;
352
371
  }
353
372
  interface SalesBookingsClient {
354
373
  /**
@@ -363,4 +382,4 @@ interface SalesBookingsClient {
363
382
  /** A fresh per-submit key. Keep it for retries of the same submit; mint a new one after. */
364
383
  declare const newBookingAttemptKey: typeof uuidV4;
365
384
 
366
- 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 BookingMode as c, type BookingOutcome as d, type BookingSimulation as e, type BookingSubmitOptions as f, type ConversionConfigStore as g, getTrackingConfigRuntime as h, resolveTrackingConfigUrl as i, type BookingDayWindows as j, type ConversionIntegrations as k, TrackingConfigRuntime as l, newBookingAttemptKey as n, resolveConversionConfig as r };
385
+ export { type BookingDropOff as B, type ConversionConfig as C, type FormSubmitField as F, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingFormSubmit as a, type BookingInput as b, type BookingMode as c, type BookingOutcome as d, type BookingSimulation as e, type BookingSubmitOptions as f, type ConversionConfigStore as g, getTrackingConfigRuntime as h, resolveTrackingConfigUrl as i, type BookingDayWindows as j, type ConversionIntegrations as k, TrackingConfigRuntime as l, newBookingAttemptKey as n, resolveConversionConfig as r };
@@ -69,6 +69,10 @@ interface BookingOptionConfig {
69
69
  /** What the site posts in `services`. */
70
70
  key: string;
71
71
  label: string;
72
+ /** The shop's service category; options arrive grouped, each category in one run. */
73
+ group: string | null;
74
+ /** Optional helper text under the label. */
75
+ description: string | null;
72
76
  }
73
77
  /** Where a booking went; decided by the API. A real customer always gets `live`. */
74
78
  declare const BOOKING_MODES: readonly ["live", "test", "simulated"];
@@ -305,7 +309,15 @@ interface BookingFormSubmit {
305
309
  };
306
310
  /** The `<form id>`, so `form_start` and `form_submit` line up. */
307
311
  formId: string;
312
+ /**
313
+ * The site's own questions (e.g. "How did you hear about us?"), appended to this
314
+ * `form_submit` for our reporting; never sent to the shop. A name the booking already
315
+ * reports (`phone`, `email`, ...) is dropped, so lead matching cannot be overridden.
316
+ */
317
+ extraFields?: FormSubmitField[];
308
318
  }
319
+ /** One field of a `form_submit`. */
320
+ type FormSubmitField = NonNullable<FormSubmitMetadata["form"]["fields"]>[number];
309
321
  type BookingOutcome =
310
322
  /** The shop has the request; it texts and emails the customer itself. */
311
323
  {
@@ -349,6 +361,13 @@ interface BookingSubmitOptions {
349
361
  * fallback screens. The production API ignores it, so it can never change a real booking.
350
362
  */
351
363
  simulate?: BookingSimulation;
364
+ /**
365
+ * Called once when the server answers `pending`, before `submit` polls for the final answer
366
+ * (up to ~30 s): show the pending success right away instead of a spinner.
367
+ */
368
+ onPending?: (outcome: Extract<BookingOutcome, {
369
+ status: "pending";
370
+ }>) => void;
352
371
  }
353
372
  interface SalesBookingsClient {
354
373
  /**
@@ -363,4 +382,4 @@ interface SalesBookingsClient {
363
382
  /** A fresh per-submit key. Keep it for retries of the same submit; mint a new one after. */
364
383
  declare const newBookingAttemptKey: typeof uuidV4;
365
384
 
366
- 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 BookingMode as c, type BookingOutcome as d, type BookingSimulation as e, type BookingSubmitOptions as f, type ConversionConfigStore as g, getTrackingConfigRuntime as h, resolveTrackingConfigUrl as i, type BookingDayWindows as j, type ConversionIntegrations as k, TrackingConfigRuntime as l, newBookingAttemptKey as n, resolveConversionConfig as r };
385
+ export { type BookingDropOff as B, type ConversionConfig as C, type FormSubmitField as F, type SalesBookingsClient as S, type TrackingConfigReference as T, type BookingFormSubmit as a, type BookingInput as b, type BookingMode as c, type BookingOutcome as d, type BookingSimulation as e, type BookingSubmitOptions as f, type ConversionConfigStore as g, getTrackingConfigRuntime as h, resolveTrackingConfigUrl as i, type BookingDayWindows as j, type ConversionIntegrations as k, TrackingConfigRuntime as l, newBookingAttemptKey as n, resolveConversionConfig as r };
package/dist/client.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
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, d as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit, e as BookingSimulation } from './bookings-CqCuuyP6.mjs';
5
- import { W as TriggerRegistryConfig, Y as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-D7UZr0EM.mjs';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, d as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit, e as BookingSimulation } from './bookings-n8UUzrak.mjs';
5
+ import { X as TriggerRegistryConfig, Z as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-ABH28Ips.mjs';
6
6
  import { CountryCode } from 'libphonenumber-js';
7
7
  import './form-submit-BLb28aCk.mjs';
8
8
  import 'zod/mini';
@@ -414,7 +414,8 @@ interface BookingSubmitState {
414
414
  /**
415
415
  * Submit state for a booking form over `salesClient.bookings`. Owns the parts that are easy to
416
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.
417
+ * `form_submit`, the stale-config refetch, and the fallback link on failure. A `pending` answer
418
+ * shows at once (`status: "pending"`) while the final one is polled; the submit stays in flight.
418
419
  */
419
420
  declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
420
421
  formSubmit?: BookingFormSubmit;
package/dist/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, InputHTMLAttributes, ChangeEvent, FocusEvent, AnchorHTMLAttributes } from 'react';
3
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, d as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit, e as BookingSimulation } from './bookings-DIu1ho6v.js';
5
- import { W as TriggerRegistryConfig, Y as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-D2S3Kb8m.js';
4
+ import { T as TrackingConfigReference, C as ConversionConfig, d as BookingOutcome, b as BookingInput, S as SalesBookingsClient, a as BookingFormSubmit, e as BookingSimulation } from './bookings-B50X7GSG.js';
5
+ import { X as TriggerRegistryConfig, Z as TypedTrackingClient, a as BookingHandoffPrefill, b as BookingIntegration, B as BookableDay } from './ingest-typed-vP-DqpxO.js';
6
6
  import { CountryCode } from 'libphonenumber-js';
7
7
  import './form-submit-BLb28aCk.js';
8
8
  import 'zod/mini';
@@ -414,7 +414,8 @@ interface BookingSubmitState {
414
414
  /**
415
415
  * Submit state for a booking form over `salesClient.bookings`. Owns the parts that are easy to
416
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.
417
+ * `form_submit`, the stale-config refetch, and the fallback link on failure. A `pending` answer
418
+ * shows at once (`status: "pending"`) while the final one is polled; the submit stays in flight.
418
419
  */
419
420
  declare function useBookingSubmit(bookings: SalesBookingsClient, options?: {
420
421
  formSubmit?: BookingFormSubmit;
package/dist/client.js CHANGED
@@ -1035,7 +1035,14 @@ function parseBookingOption(value) {
1035
1035
  if (!value || typeof value !== "object") return [];
1036
1036
  const o = value;
1037
1037
  if (typeof o.key !== "string" || !o.key || typeof o.label !== "string") return [];
1038
- return [{ key: o.key, label: o.label }];
1038
+ return [
1039
+ {
1040
+ key: o.key,
1041
+ label: o.label,
1042
+ group: typeof o.group === "string" && o.group ? o.group : null,
1043
+ description: typeof o.description === "string" && o.description ? o.description : null
1044
+ }
1045
+ ];
1039
1046
  }
1040
1047
  function parseIntegrations(value) {
1041
1048
  if (!value || typeof value !== "object") return {};
@@ -1739,7 +1746,12 @@ function getBookingIntegration(integrations) {
1739
1746
  timezone: booking.timezone,
1740
1747
  configVersion: booking.config_version,
1741
1748
  rules,
1742
- options: booking.options.map((option) => ({ key: option.key, label: option.label })),
1749
+ options: booking.options.map((option) => ({
1750
+ key: option.key,
1751
+ label: option.label,
1752
+ group: option.group,
1753
+ description: option.description
1754
+ })),
1743
1755
  mode: booking.mode ?? "live"
1744
1756
  };
1745
1757
  }
@@ -3973,7 +3985,7 @@ function GoogleAdsTracking(props) {
3973
3985
  var import_react6 = require("react");
3974
3986
 
3975
3987
  // package.json
3976
- var version = "1.4.1";
3988
+ var version = "1.6.0";
3977
3989
 
3978
3990
  // ../tracking-core/src/phone-react.tsx
3979
3991
  var import_react5 = require("react");
@@ -4296,7 +4308,16 @@ function useBookingSubmit(bookings, options = {}) {
4296
4308
  if (inFlight.current) return inFlight.current;
4297
4309
  attemptKey.current ?? (attemptKey.current = newBookingAttemptKey());
4298
4310
  setSubmitting(true);
4299
- const run = bookings.submit(input, { attemptKey: attemptKey.current, formSubmit, simulate }).then((result) => {
4311
+ const run = bookings.submit(input, {
4312
+ attemptKey: attemptKey.current,
4313
+ formSubmit,
4314
+ simulate,
4315
+ // The server has it: show the pending success while the final answer is polled.
4316
+ onPending: (pending) => {
4317
+ setOutcome(pending);
4318
+ setSubmitting(false);
4319
+ }
4320
+ }).then((result) => {
4300
4321
  const unrecorded = result.status === "invalid" || result.status === "stale" || result.status === "failed" && result.id === null;
4301
4322
  if (!unrecorded) attemptKey.current = null;
4302
4323
  setOutcome(result);