@aranova/tracking-next 0.8.0 → 0.9.1

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
@@ -216,9 +216,65 @@ npx @aranova/tracking-cli gen # reads ARANOVA_TRACKING_SECRET_KEY from
216
216
  See the full guide: [sales-tracking.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/sales-tracking.md)
217
217
  and the CLI reference: [cli.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/cli.md).
218
218
 
219
+ ## Consent UI
220
+
221
+ The bundled `<ConsentBanner />` renders a non-blocking bottom-docked banner while consent is `pending`, persists the visitor's choice to `localStorage`, and propagates it to Google Consent Mode v2 when gtag is loaded. **Inline-styled** — no Tailwind or CSS imports required at the consumer.
222
+
223
+ ```tsx
224
+ import { ConsentBanner } from '@aranova/tracking-next';
225
+
226
+ // Drop-in (already wired in the root layout example above)
227
+ <ConsentBanner />
228
+ ```
229
+
230
+ All props are optional:
231
+
232
+ ```tsx
233
+ <ConsentBanner
234
+ title="Cookies"
235
+ message="We use cookies to track ad performance."
236
+ acceptLabel="Sure"
237
+ declineLabel="No thanks"
238
+ policyHref="/privacy"
239
+ policyLabel="Privacy policy" // default: "Learn more"
240
+ onAccept={() => track('consent_accepted')}
241
+ onDecline={() => track('consent_declined')}
242
+ position="bottom" // or "top"
243
+ theme="light" // "light" | "dark" | "auto"
244
+ className="my-extra-classes"
245
+ style={{ background: '#fafafa' }} // wins over the theme defaults
246
+ />
247
+ ```
248
+
249
+ ### Fully custom UI — `useConsent()`
250
+
251
+ For a bespoke banner, skip the component and drive your own UI with the headless hook:
252
+
253
+ ```tsx
254
+ 'use client';
255
+ import { useConsent } from '@aranova/tracking-next';
256
+
257
+ function CookieBar() {
258
+ const { state, accept, decline, reset, isPending } = useConsent();
259
+
260
+ if (!isPending) {
261
+ // Footer link: re-open the banner if they change their mind.
262
+ return <button onClick={reset}>Cookie preferences</button>;
263
+ }
264
+ return (
265
+ <MyBespokeBanner>
266
+ <button onClick={decline}>No thanks</button>
267
+ <button onClick={accept}>Sure</button>
268
+ </MyBespokeBanner>
269
+ );
270
+ }
271
+ ```
272
+
273
+ The hook handles localStorage persistence, gtag sync, and cross-tab propagation — same machinery the default banner uses. `resetConsent()` is also exported as a standalone for non-component contexts (server actions that revalidate after an account change, etc.).
274
+
219
275
  ## Exports
220
276
 
221
- - Root package: `createTracking`, `TrackingProvider`, `useTracking`, `GoogleAdsTracking`, `ConsentBanner`, hooks, event types, and `createSalesClient()` (isomorphic sales client — public key writes, secret key reads/CRUD) + money helpers (`toMinor`/`fromMinor`/`formatMoney`)
277
+ - Root package: `createTracking`, `TrackingProvider`, `useTracking`, `GoogleAdsTracking`, `ConsentBanner` (+ `ConsentBannerProps`), `useConsent` (+ `UseConsentResult`), `useConsentState`, `useGclid`, `useTrackingParams`, standalone consent helpers (`getConsentState`, `setConsentState`, `resetConsent`), event types, and `createSalesClient()` (isomorphic sales client — public key writes, secret key reads/CRUD) + money helpers (`toMinor`/`fromMinor`/`formatMoney`)
222
278
  - `@aranova/tracking-next/middleware`: `createTrackingMiddleware()`
223
279
  - `@aranova/tracking-next/server`: `getTrackingParamsServer()`
224
280
  - Codegen: [`@aranova/tracking-cli`](https://www.npmjs.com/package/@aranova/tracking-cli) — `gen` typed service unions (devDependency)
package/dist/index.d.mts CHANGED
@@ -2,8 +2,8 @@ import { C as ConsentState, T as TrackingInstallSurface, a as TrackingEnvironmen
2
2
  export { f as TrackingInitConfig } from './types-BPJLgMsG.mjs';
3
3
  import { z } from 'zod';
4
4
  import * as src from 'src';
5
+ import { ReactNode, CSSProperties } from 'react';
5
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
- import { ReactNode } from 'react';
7
7
 
8
8
  /**
9
9
  * Google Consent Mode value sent to `gtag('consent', 'update', ...)`.
@@ -21,6 +21,22 @@ declare function getConsentState(): ConsentState;
21
21
  * loaded.
22
22
  */
23
23
  declare function setConsentState(state: GtagConsentValue): void;
24
+ /**
25
+ * Clear the stored consent choice so the banner re-appears on next render.
26
+ *
27
+ * Power a "Cookie preferences" link in a footer so visitors can change their
28
+ * mind without losing access to your site:
29
+ *
30
+ * ```tsx
31
+ * const { reset } = useConsent();
32
+ * <button onClick={reset}>Cookie preferences</button>
33
+ * ```
34
+ *
35
+ * Does NOT push an `update` to gtag — there's nothing to update because the
36
+ * visitor hasn't chosen anything yet. The next `setConsentState()` call will
37
+ * sync gtag once they re-choose.
38
+ */
39
+ declare function resetConsent(): void;
24
40
 
25
41
  interface TrackingContextInput {
26
42
  packageName?: string | null;
@@ -1298,6 +1314,9 @@ declare const saleCreateSchema: z.ZodObject<{
1298
1314
  unit_cost_cents?: number | null | undefined;
1299
1315
  }>, "many">>;
1300
1316
  metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1317
+ customer_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1318
+ customer_phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1319
+ customer_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1301
1320
  }, "strict", z.ZodTypeAny, {
1302
1321
  currency: "USD" | "CAD";
1303
1322
  amount_total_cents: number;
@@ -1315,6 +1334,9 @@ declare const saleCreateSchema: z.ZodObject<{
1315
1334
  external_id?: string | null | undefined;
1316
1335
  description?: string | null | undefined;
1317
1336
  service?: string | null | undefined;
1337
+ customer_name?: string | null | undefined;
1338
+ customer_phone?: string | null | undefined;
1339
+ customer_email?: string | null | undefined;
1318
1340
  }, {
1319
1341
  currency: "USD" | "CAD";
1320
1342
  amount_total_cents: number;
@@ -1332,6 +1354,9 @@ declare const saleCreateSchema: z.ZodObject<{
1332
1354
  category?: string | null | undefined;
1333
1355
  unit_cost_cents?: number | null | undefined;
1334
1356
  }[] | undefined;
1357
+ customer_name?: string | null | undefined;
1358
+ customer_phone?: string | null | undefined;
1359
+ customer_email?: string | null | undefined;
1335
1360
  }>;
1336
1361
  declare const saleUpdateSchema: z.ZodObject<{
1337
1362
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1362,6 +1387,9 @@ declare const saleUpdateSchema: z.ZodObject<{
1362
1387
  unit_cost_cents?: number | null | undefined;
1363
1388
  }>, "many">>;
1364
1389
  metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1390
+ customer_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1391
+ customer_phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1392
+ customer_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1365
1393
  }, "strict", z.ZodTypeAny, {
1366
1394
  metadata?: Record<string, unknown> | null | undefined;
1367
1395
  description?: string | null | undefined;
@@ -1377,6 +1405,9 @@ declare const saleUpdateSchema: z.ZodObject<{
1377
1405
  category?: string | null | undefined;
1378
1406
  unit_cost_cents?: number | null | undefined;
1379
1407
  }[] | undefined;
1408
+ customer_name?: string | null | undefined;
1409
+ customer_phone?: string | null | undefined;
1410
+ customer_email?: string | null | undefined;
1380
1411
  }, {
1381
1412
  metadata?: Record<string, unknown> | null | undefined;
1382
1413
  description?: string | null | undefined;
@@ -1392,6 +1423,9 @@ declare const saleUpdateSchema: z.ZodObject<{
1392
1423
  category?: string | null | undefined;
1393
1424
  unit_cost_cents?: number | null | undefined;
1394
1425
  }[] | undefined;
1426
+ customer_name?: string | null | undefined;
1427
+ customer_phone?: string | null | undefined;
1428
+ customer_email?: string | null | undefined;
1395
1429
  }>;
1396
1430
  type SaleItemInput = z.input<typeof saleItemSchema>;
1397
1431
  type SaleInput = z.input<typeof saleCreateSchema>;
@@ -1419,6 +1453,9 @@ interface Sale {
1419
1453
  occurred_at: string;
1420
1454
  environment: (typeof TRACKING_ENVIRONMENTS)[number];
1421
1455
  metadata: Record<string, unknown> | null;
1456
+ customer_name: string | null;
1457
+ customer_phone: string | null;
1458
+ customer_email: string | null;
1422
1459
  created_at: string;
1423
1460
  updated_at: string;
1424
1461
  items: SaleItem[];
@@ -1431,13 +1468,38 @@ interface SaleCursorPage {
1431
1468
  items: Sale[];
1432
1469
  next_cursor: string | null;
1433
1470
  }
1434
- interface SaleListQuery {
1471
+ /**
1472
+ * Comprehensive filter shape mirrored from the backend's `SaleQueryFilters`.
1473
+ *
1474
+ * `search` runs case-insensitively across `customer_name`, `customer_phone`,
1475
+ * `customer_email`, `description`, and `external_id` — the human-facing
1476
+ * columns. `service_id` is the resolved per-business service UUID (different
1477
+ * from the create-time `service` *key*).
1478
+ */
1479
+ interface SaleFilters {
1435
1480
  business_id?: string;
1436
1481
  external_id?: string;
1482
+ service_id?: string;
1437
1483
  currency?: SupportedCurrency;
1438
1484
  environment?: (typeof TRACKING_ENVIRONMENTS)[number];
1439
1485
  since?: string;
1440
1486
  until?: string;
1487
+ min_amount_cents?: number;
1488
+ max_amount_cents?: number;
1489
+ search?: string;
1490
+ }
1491
+ /**
1492
+ * Full query input for `SalesClient.list()` — filters + pagination.
1493
+ *
1494
+ * Pagination is **keyset (cursor)**: `next_cursor` returned by one page is
1495
+ * passed back as `cursor` on the next. `null` / undefined cursor = first page.
1496
+ *
1497
+ * Ordering on this endpoint is fixed at **`occurred_at DESC, id DESC`** — the
1498
+ * cursor encodes a position in that index, so a different sort would
1499
+ * invalidate cursors mid-pagination. For ad-hoc sorted reads use the
1500
+ * dashboard admin endpoint, which is offset-paginated.
1501
+ */
1502
+ interface SaleListQuery extends SaleFilters {
1441
1503
  limit?: number;
1442
1504
  cursor?: string | null;
1443
1505
  }
@@ -1534,12 +1596,69 @@ declare class AranovaApiError extends Error {
1534
1596
  }
1535
1597
 
1536
1598
  /**
1537
- * Default non-blocking consent banner for Next.js installs.
1599
+ * Default non-blocking consent banner.
1600
+ *
1601
+ * Renders only while consent is `pending`; collapses to `null` once the
1602
+ * visitor has chosen.
1603
+ *
1604
+ * **Styling is intentionally self-contained** — inline styles, zero CSS
1605
+ * dependencies, no Tailwind required at the consumer. The Tailwind-based
1606
+ * banner shipped before 0.9.1 rendered as transparent in any consumer that
1607
+ * didn't configure their content array to scan
1608
+ * `node_modules/@aranova/tracking-next/dist/**`; this version sidesteps that
1609
+ * class of bug entirely.
1610
+ *
1611
+ * For a fully bespoke banner, skip this component and use {@link useConsent}
1612
+ * directly to drive your own UI.
1538
1613
  *
1539
- * Renders only while consent is `pending`. Accept/decline choices are stored
1540
- * in localStorage and propagated to Google Consent Mode when gtag is loaded.
1614
+ * @example
1615
+ * // Drop-in default
1616
+ * <ConsentBanner />
1617
+ *
1618
+ * @example
1619
+ * // Customized
1620
+ * <ConsentBanner
1621
+ * message="We use cookies to learn which ads drive bookings."
1622
+ * acceptLabel="Sounds good"
1623
+ * declineLabel="No thanks"
1624
+ * policyHref="/privacy"
1625
+ * policyLabel="Privacy policy"
1626
+ * theme="dark"
1627
+ * onAccept={() => track('consent_accepted')}
1628
+ * onDecline={() => track('consent_declined')}
1629
+ * />
1541
1630
  */
1542
- declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
1631
+ interface ConsentBannerProps {
1632
+ /** Body text. Defaults to the standard cookies-for-ad-performance message. */
1633
+ message?: ReactNode;
1634
+ /** Optional bold title above the body text. */
1635
+ title?: ReactNode;
1636
+ /** Label for the accept button. Default: `"Accept"`. */
1637
+ acceptLabel?: string;
1638
+ /** Label for the decline button. Default: `"Decline"`. */
1639
+ declineLabel?: string;
1640
+ /** Optional link inline with the message (e.g. to a privacy policy). */
1641
+ policyHref?: string;
1642
+ /** Visible text for {@link policyHref}. Default: `"Learn more"`. */
1643
+ policyLabel?: string;
1644
+ /**
1645
+ * Fires after the consent state is persisted + propagated to gtag. Useful
1646
+ * for emitting your own analytics event on the choice.
1647
+ */
1648
+ onAccept?: () => void;
1649
+ onDecline?: () => void;
1650
+ /** Where the banner docks. Default: `"bottom"`. */
1651
+ position?: 'top' | 'bottom';
1652
+ /**
1653
+ * Visual theme. `"auto"` follows `prefers-color-scheme`. Default: `"light"`.
1654
+ */
1655
+ theme?: 'light' | 'dark' | 'auto';
1656
+ /** Class added to the outer wrapper for additional styling hooks. */
1657
+ className?: string;
1658
+ /** Inline style overrides applied to the outer wrapper after the defaults. */
1659
+ style?: CSSProperties;
1660
+ }
1661
+ declare function ConsentBanner({ message, title, acceptLabel, declineLabel, policyHref, policyLabel, onAccept, onDecline, position, theme, className, style, }?: ConsentBannerProps): ReactNode;
1543
1662
 
1544
1663
  /**
1545
1664
  * Read the captured Google Ads click id from first-party cookies.
@@ -1556,8 +1675,46 @@ declare function useTrackingParams(): TrackingParams;
1556
1675
  /**
1557
1676
  * Read the current visitor consent state and update when another tab changes
1558
1677
  * the stored value.
1678
+ *
1679
+ * Prefer {@link useConsent} for new code — it returns the same state plus
1680
+ * the `accept` / `decline` / `reset` actions a custom consent UI needs.
1681
+ * `useConsentState` is kept as a convenience for callers that only need to
1682
+ * read.
1559
1683
  */
1560
1684
  declare function useConsentState(): ConsentState;
1685
+ /**
1686
+ * The headless consent surface — state + actions in one hook.
1687
+ *
1688
+ * Build a fully-custom banner without losing the gtag-sync, localStorage
1689
+ * persistence, or cross-tab propagation:
1690
+ *
1691
+ * ```tsx
1692
+ * const { state, accept, decline, reset, isPending } = useConsent();
1693
+ *
1694
+ * if (!isPending) {
1695
+ * return <button onClick={reset}>Cookie preferences</button>;
1696
+ * }
1697
+ * return (
1698
+ * <MyBannerStyling>
1699
+ * <button onClick={decline}>No thanks</button>
1700
+ * <button onClick={accept}>Sure</button>
1701
+ * </MyBannerStyling>
1702
+ * );
1703
+ * ```
1704
+ *
1705
+ * The boolean helpers (`isPending` / `isGranted` / `isDenied`) are equivalent
1706
+ * to comparing `state` directly — they're there for readability at call sites.
1707
+ */
1708
+ interface UseConsentResult {
1709
+ state: ConsentState;
1710
+ isPending: boolean;
1711
+ isGranted: boolean;
1712
+ isDenied: boolean;
1713
+ accept: () => void;
1714
+ decline: () => void;
1715
+ reset: () => void;
1716
+ }
1717
+ declare function useConsent(): UseConsentResult;
1561
1718
 
1562
1719
  /**
1563
1720
  * Props for the Next.js Google Ads tracking component.
@@ -1646,4 +1803,4 @@ interface CreateTrackingResult<TRegistry extends TriggerRegistryConfig> {
1646
1803
  */
1647
1804
  declare function createTracking<TRegistry extends TriggerRegistryConfig>(options: CreateTrackingOptions<TRegistry>): CreateTrackingResult<TRegistry>;
1648
1805
 
1649
- export { AranovaApiError, type AutomaticEventName, ConsentBanner, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type EventConfig, type EventMetadata, type EventName, type FormStartConfig, type FormStartMetadata, type FormSubmitConfig, type FormSubmitMetadata, GoogleAdsTracking, type GoogleAdsTrackingProps, type JsonValue, type ManualEventName, type MultiPageSessionConfig, type MultiPageSessionMetadata, type PageViewConfig, type PageViewMetadata, type PhoneClickConfig, type PhoneClickMetadata, type PublicServiceItem, type RegisteredAutomaticEvents, type RegisteredManualEvents, type Sale, type SaleCursorPage, type SaleInput, type SaleItem, type SaleItemInput, type SaleListPage, type SaleListQuery, type SaleUpdateInput, type SalesClient, type SalesClientConfig, type SalesTransportConfig, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, type SupportedCurrency, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, TrackingParams, type TrackingProviderProps, TrackingSessionUpsertPayload, type TriggerRegistryConfig, type TypedTrackEventOptions, type TypedTrackingClient, captureTrackingParamsFromLocation, createSalesClient, createTracking, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, fetchServices, formatMoney, fromMinor, getConsentState, setConsentState, toMinor, useConsentState, useGclid, useTrackingParams };
1806
+ export { AranovaApiError, type AutomaticEventName, ConsentBanner, type ConsentBannerProps, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type EventConfig, type EventMetadata, type EventName, type FormStartConfig, type FormStartMetadata, type FormSubmitConfig, type FormSubmitMetadata, GoogleAdsTracking, type GoogleAdsTrackingProps, type JsonValue, type ManualEventName, type MultiPageSessionConfig, type MultiPageSessionMetadata, type PageViewConfig, type PageViewMetadata, type PhoneClickConfig, type PhoneClickMetadata, type PublicServiceItem, type RegisteredAutomaticEvents, type RegisteredManualEvents, type Sale, type SaleCursorPage, type SaleInput, type SaleItem, type SaleItemInput, type SaleListPage, type SaleListQuery, type SaleUpdateInput, type SalesClient, type SalesClientConfig, type SalesTransportConfig, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, type SupportedCurrency, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, TrackingParams, type TrackingProviderProps, TrackingSessionUpsertPayload, type TriggerRegistryConfig, type TypedTrackEventOptions, type TypedTrackingClient, type UseConsentResult, captureTrackingParamsFromLocation, createSalesClient, createTracking, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, fetchServices, formatMoney, fromMinor, getConsentState, resetConsent, setConsentState, toMinor, useConsent, useConsentState, useGclid, useTrackingParams };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { C as ConsentState, T as TrackingInstallSurface, a as TrackingEnvironmen
2
2
  export { f as TrackingInitConfig } from './types-BPJLgMsG.js';
3
3
  import { z } from 'zod';
4
4
  import * as src from 'src';
5
+ import { ReactNode, CSSProperties } from 'react';
5
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
- import { ReactNode } from 'react';
7
7
 
8
8
  /**
9
9
  * Google Consent Mode value sent to `gtag('consent', 'update', ...)`.
@@ -21,6 +21,22 @@ declare function getConsentState(): ConsentState;
21
21
  * loaded.
22
22
  */
23
23
  declare function setConsentState(state: GtagConsentValue): void;
24
+ /**
25
+ * Clear the stored consent choice so the banner re-appears on next render.
26
+ *
27
+ * Power a "Cookie preferences" link in a footer so visitors can change their
28
+ * mind without losing access to your site:
29
+ *
30
+ * ```tsx
31
+ * const { reset } = useConsent();
32
+ * <button onClick={reset}>Cookie preferences</button>
33
+ * ```
34
+ *
35
+ * Does NOT push an `update` to gtag — there's nothing to update because the
36
+ * visitor hasn't chosen anything yet. The next `setConsentState()` call will
37
+ * sync gtag once they re-choose.
38
+ */
39
+ declare function resetConsent(): void;
24
40
 
25
41
  interface TrackingContextInput {
26
42
  packageName?: string | null;
@@ -1298,6 +1314,9 @@ declare const saleCreateSchema: z.ZodObject<{
1298
1314
  unit_cost_cents?: number | null | undefined;
1299
1315
  }>, "many">>;
1300
1316
  metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1317
+ customer_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1318
+ customer_phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1319
+ customer_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1301
1320
  }, "strict", z.ZodTypeAny, {
1302
1321
  currency: "USD" | "CAD";
1303
1322
  amount_total_cents: number;
@@ -1315,6 +1334,9 @@ declare const saleCreateSchema: z.ZodObject<{
1315
1334
  external_id?: string | null | undefined;
1316
1335
  description?: string | null | undefined;
1317
1336
  service?: string | null | undefined;
1337
+ customer_name?: string | null | undefined;
1338
+ customer_phone?: string | null | undefined;
1339
+ customer_email?: string | null | undefined;
1318
1340
  }, {
1319
1341
  currency: "USD" | "CAD";
1320
1342
  amount_total_cents: number;
@@ -1332,6 +1354,9 @@ declare const saleCreateSchema: z.ZodObject<{
1332
1354
  category?: string | null | undefined;
1333
1355
  unit_cost_cents?: number | null | undefined;
1334
1356
  }[] | undefined;
1357
+ customer_name?: string | null | undefined;
1358
+ customer_phone?: string | null | undefined;
1359
+ customer_email?: string | null | undefined;
1335
1360
  }>;
1336
1361
  declare const saleUpdateSchema: z.ZodObject<{
1337
1362
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -1362,6 +1387,9 @@ declare const saleUpdateSchema: z.ZodObject<{
1362
1387
  unit_cost_cents?: number | null | undefined;
1363
1388
  }>, "many">>;
1364
1389
  metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1390
+ customer_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1391
+ customer_phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1392
+ customer_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1365
1393
  }, "strict", z.ZodTypeAny, {
1366
1394
  metadata?: Record<string, unknown> | null | undefined;
1367
1395
  description?: string | null | undefined;
@@ -1377,6 +1405,9 @@ declare const saleUpdateSchema: z.ZodObject<{
1377
1405
  category?: string | null | undefined;
1378
1406
  unit_cost_cents?: number | null | undefined;
1379
1407
  }[] | undefined;
1408
+ customer_name?: string | null | undefined;
1409
+ customer_phone?: string | null | undefined;
1410
+ customer_email?: string | null | undefined;
1380
1411
  }, {
1381
1412
  metadata?: Record<string, unknown> | null | undefined;
1382
1413
  description?: string | null | undefined;
@@ -1392,6 +1423,9 @@ declare const saleUpdateSchema: z.ZodObject<{
1392
1423
  category?: string | null | undefined;
1393
1424
  unit_cost_cents?: number | null | undefined;
1394
1425
  }[] | undefined;
1426
+ customer_name?: string | null | undefined;
1427
+ customer_phone?: string | null | undefined;
1428
+ customer_email?: string | null | undefined;
1395
1429
  }>;
1396
1430
  type SaleItemInput = z.input<typeof saleItemSchema>;
1397
1431
  type SaleInput = z.input<typeof saleCreateSchema>;
@@ -1419,6 +1453,9 @@ interface Sale {
1419
1453
  occurred_at: string;
1420
1454
  environment: (typeof TRACKING_ENVIRONMENTS)[number];
1421
1455
  metadata: Record<string, unknown> | null;
1456
+ customer_name: string | null;
1457
+ customer_phone: string | null;
1458
+ customer_email: string | null;
1422
1459
  created_at: string;
1423
1460
  updated_at: string;
1424
1461
  items: SaleItem[];
@@ -1431,13 +1468,38 @@ interface SaleCursorPage {
1431
1468
  items: Sale[];
1432
1469
  next_cursor: string | null;
1433
1470
  }
1434
- interface SaleListQuery {
1471
+ /**
1472
+ * Comprehensive filter shape mirrored from the backend's `SaleQueryFilters`.
1473
+ *
1474
+ * `search` runs case-insensitively across `customer_name`, `customer_phone`,
1475
+ * `customer_email`, `description`, and `external_id` — the human-facing
1476
+ * columns. `service_id` is the resolved per-business service UUID (different
1477
+ * from the create-time `service` *key*).
1478
+ */
1479
+ interface SaleFilters {
1435
1480
  business_id?: string;
1436
1481
  external_id?: string;
1482
+ service_id?: string;
1437
1483
  currency?: SupportedCurrency;
1438
1484
  environment?: (typeof TRACKING_ENVIRONMENTS)[number];
1439
1485
  since?: string;
1440
1486
  until?: string;
1487
+ min_amount_cents?: number;
1488
+ max_amount_cents?: number;
1489
+ search?: string;
1490
+ }
1491
+ /**
1492
+ * Full query input for `SalesClient.list()` — filters + pagination.
1493
+ *
1494
+ * Pagination is **keyset (cursor)**: `next_cursor` returned by one page is
1495
+ * passed back as `cursor` on the next. `null` / undefined cursor = first page.
1496
+ *
1497
+ * Ordering on this endpoint is fixed at **`occurred_at DESC, id DESC`** — the
1498
+ * cursor encodes a position in that index, so a different sort would
1499
+ * invalidate cursors mid-pagination. For ad-hoc sorted reads use the
1500
+ * dashboard admin endpoint, which is offset-paginated.
1501
+ */
1502
+ interface SaleListQuery extends SaleFilters {
1441
1503
  limit?: number;
1442
1504
  cursor?: string | null;
1443
1505
  }
@@ -1534,12 +1596,69 @@ declare class AranovaApiError extends Error {
1534
1596
  }
1535
1597
 
1536
1598
  /**
1537
- * Default non-blocking consent banner for Next.js installs.
1599
+ * Default non-blocking consent banner.
1600
+ *
1601
+ * Renders only while consent is `pending`; collapses to `null` once the
1602
+ * visitor has chosen.
1603
+ *
1604
+ * **Styling is intentionally self-contained** — inline styles, zero CSS
1605
+ * dependencies, no Tailwind required at the consumer. The Tailwind-based
1606
+ * banner shipped before 0.9.1 rendered as transparent in any consumer that
1607
+ * didn't configure their content array to scan
1608
+ * `node_modules/@aranova/tracking-next/dist/**`; this version sidesteps that
1609
+ * class of bug entirely.
1610
+ *
1611
+ * For a fully bespoke banner, skip this component and use {@link useConsent}
1612
+ * directly to drive your own UI.
1538
1613
  *
1539
- * Renders only while consent is `pending`. Accept/decline choices are stored
1540
- * in localStorage and propagated to Google Consent Mode when gtag is loaded.
1614
+ * @example
1615
+ * // Drop-in default
1616
+ * <ConsentBanner />
1617
+ *
1618
+ * @example
1619
+ * // Customized
1620
+ * <ConsentBanner
1621
+ * message="We use cookies to learn which ads drive bookings."
1622
+ * acceptLabel="Sounds good"
1623
+ * declineLabel="No thanks"
1624
+ * policyHref="/privacy"
1625
+ * policyLabel="Privacy policy"
1626
+ * theme="dark"
1627
+ * onAccept={() => track('consent_accepted')}
1628
+ * onDecline={() => track('consent_declined')}
1629
+ * />
1541
1630
  */
1542
- declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
1631
+ interface ConsentBannerProps {
1632
+ /** Body text. Defaults to the standard cookies-for-ad-performance message. */
1633
+ message?: ReactNode;
1634
+ /** Optional bold title above the body text. */
1635
+ title?: ReactNode;
1636
+ /** Label for the accept button. Default: `"Accept"`. */
1637
+ acceptLabel?: string;
1638
+ /** Label for the decline button. Default: `"Decline"`. */
1639
+ declineLabel?: string;
1640
+ /** Optional link inline with the message (e.g. to a privacy policy). */
1641
+ policyHref?: string;
1642
+ /** Visible text for {@link policyHref}. Default: `"Learn more"`. */
1643
+ policyLabel?: string;
1644
+ /**
1645
+ * Fires after the consent state is persisted + propagated to gtag. Useful
1646
+ * for emitting your own analytics event on the choice.
1647
+ */
1648
+ onAccept?: () => void;
1649
+ onDecline?: () => void;
1650
+ /** Where the banner docks. Default: `"bottom"`. */
1651
+ position?: 'top' | 'bottom';
1652
+ /**
1653
+ * Visual theme. `"auto"` follows `prefers-color-scheme`. Default: `"light"`.
1654
+ */
1655
+ theme?: 'light' | 'dark' | 'auto';
1656
+ /** Class added to the outer wrapper for additional styling hooks. */
1657
+ className?: string;
1658
+ /** Inline style overrides applied to the outer wrapper after the defaults. */
1659
+ style?: CSSProperties;
1660
+ }
1661
+ declare function ConsentBanner({ message, title, acceptLabel, declineLabel, policyHref, policyLabel, onAccept, onDecline, position, theme, className, style, }?: ConsentBannerProps): ReactNode;
1543
1662
 
1544
1663
  /**
1545
1664
  * Read the captured Google Ads click id from first-party cookies.
@@ -1556,8 +1675,46 @@ declare function useTrackingParams(): TrackingParams;
1556
1675
  /**
1557
1676
  * Read the current visitor consent state and update when another tab changes
1558
1677
  * the stored value.
1678
+ *
1679
+ * Prefer {@link useConsent} for new code — it returns the same state plus
1680
+ * the `accept` / `decline` / `reset` actions a custom consent UI needs.
1681
+ * `useConsentState` is kept as a convenience for callers that only need to
1682
+ * read.
1559
1683
  */
1560
1684
  declare function useConsentState(): ConsentState;
1685
+ /**
1686
+ * The headless consent surface — state + actions in one hook.
1687
+ *
1688
+ * Build a fully-custom banner without losing the gtag-sync, localStorage
1689
+ * persistence, or cross-tab propagation:
1690
+ *
1691
+ * ```tsx
1692
+ * const { state, accept, decline, reset, isPending } = useConsent();
1693
+ *
1694
+ * if (!isPending) {
1695
+ * return <button onClick={reset}>Cookie preferences</button>;
1696
+ * }
1697
+ * return (
1698
+ * <MyBannerStyling>
1699
+ * <button onClick={decline}>No thanks</button>
1700
+ * <button onClick={accept}>Sure</button>
1701
+ * </MyBannerStyling>
1702
+ * );
1703
+ * ```
1704
+ *
1705
+ * The boolean helpers (`isPending` / `isGranted` / `isDenied`) are equivalent
1706
+ * to comparing `state` directly — they're there for readability at call sites.
1707
+ */
1708
+ interface UseConsentResult {
1709
+ state: ConsentState;
1710
+ isPending: boolean;
1711
+ isGranted: boolean;
1712
+ isDenied: boolean;
1713
+ accept: () => void;
1714
+ decline: () => void;
1715
+ reset: () => void;
1716
+ }
1717
+ declare function useConsent(): UseConsentResult;
1561
1718
 
1562
1719
  /**
1563
1720
  * Props for the Next.js Google Ads tracking component.
@@ -1646,4 +1803,4 @@ interface CreateTrackingResult<TRegistry extends TriggerRegistryConfig> {
1646
1803
  */
1647
1804
  declare function createTracking<TRegistry extends TriggerRegistryConfig>(options: CreateTrackingOptions<TRegistry>): CreateTrackingResult<TRegistry>;
1648
1805
 
1649
- export { AranovaApiError, type AutomaticEventName, ConsentBanner, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type EventConfig, type EventMetadata, type EventName, type FormStartConfig, type FormStartMetadata, type FormSubmitConfig, type FormSubmitMetadata, GoogleAdsTracking, type GoogleAdsTrackingProps, type JsonValue, type ManualEventName, type MultiPageSessionConfig, type MultiPageSessionMetadata, type PageViewConfig, type PageViewMetadata, type PhoneClickConfig, type PhoneClickMetadata, type PublicServiceItem, type RegisteredAutomaticEvents, type RegisteredManualEvents, type Sale, type SaleCursorPage, type SaleInput, type SaleItem, type SaleItemInput, type SaleListPage, type SaleListQuery, type SaleUpdateInput, type SalesClient, type SalesClientConfig, type SalesTransportConfig, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, type SupportedCurrency, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, TrackingParams, type TrackingProviderProps, TrackingSessionUpsertPayload, type TriggerRegistryConfig, type TypedTrackEventOptions, type TypedTrackingClient, captureTrackingParamsFromLocation, createSalesClient, createTracking, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, fetchServices, formatMoney, fromMinor, getConsentState, setConsentState, toMinor, useConsentState, useGclid, useTrackingParams };
1806
+ export { AranovaApiError, type AutomaticEventName, ConsentBanner, type ConsentBannerProps, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type EventConfig, type EventMetadata, type EventName, type FormStartConfig, type FormStartMetadata, type FormSubmitConfig, type FormSubmitMetadata, GoogleAdsTracking, type GoogleAdsTrackingProps, type JsonValue, type ManualEventName, type MultiPageSessionConfig, type MultiPageSessionMetadata, type PageViewConfig, type PageViewMetadata, type PhoneClickConfig, type PhoneClickMetadata, type PublicServiceItem, type RegisteredAutomaticEvents, type RegisteredManualEvents, type Sale, type SaleCursorPage, type SaleInput, type SaleItem, type SaleItemInput, type SaleListPage, type SaleListQuery, type SaleUpdateInput, type SalesClient, type SalesClientConfig, type SalesTransportConfig, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, type SupportedCurrency, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, TrackingParams, type TrackingProviderProps, TrackingSessionUpsertPayload, type TriggerRegistryConfig, type TypedTrackEventOptions, type TypedTrackingClient, type UseConsentResult, captureTrackingParamsFromLocation, createSalesClient, createTracking, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, fetchServices, formatMoney, fromMinor, getConsentState, resetConsent, setConsentState, toMinor, useConsent, useConsentState, useGclid, useTrackingParams };