@aranova/tracking-next 0.9.0 → 0.10.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
@@ -200,10 +200,15 @@ const sales = createSalesClient<AranovaService>({
200
200
  endpoint: process.env.ARANOVA_TRACKING_ENDPOINT!,
201
201
  });
202
202
  const { items, next_cursor } = await sales.list({ limit: 50 });
203
+ const summary = await sales.summary({ range: '30d' }); // currency-grouped aggregations
203
204
  ```
204
205
 
205
- A public-key client calling `list`/`get`/`update`/`delete` gets a `403` telling it
206
- to use a secret key server-side.
206
+ `sales.summary({ range })` returns revenue and average order value per currency,
207
+ distinct customers, by-service / by-currency / by-category breakdowns, and a
208
+ revenue/count trend — a secret-key read scoped to the key's business.
209
+
210
+ A public-key client calling `list`/`summary`/`get`/`update`/`delete` gets a `403`
211
+ telling it to use a secret key server-side.
207
212
 
208
213
  Generate the typed `AranovaService` union from your dashboard services with the CLI
209
214
  (install it as a **devDependency**):
@@ -216,9 +221,65 @@ npx @aranova/tracking-cli gen # reads ARANOVA_TRACKING_SECRET_KEY from
216
221
  See the full guide: [sales-tracking.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/sales-tracking.md)
217
222
  and the CLI reference: [cli.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/cli.md).
218
223
 
224
+ ## Consent UI
225
+
226
+ 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.
227
+
228
+ ```tsx
229
+ import { ConsentBanner } from '@aranova/tracking-next';
230
+
231
+ // Drop-in (already wired in the root layout example above)
232
+ <ConsentBanner />
233
+ ```
234
+
235
+ All props are optional:
236
+
237
+ ```tsx
238
+ <ConsentBanner
239
+ title="Cookies"
240
+ message="We use cookies to track ad performance."
241
+ acceptLabel="Sure"
242
+ declineLabel="No thanks"
243
+ policyHref="/privacy"
244
+ policyLabel="Privacy policy" // default: "Learn more"
245
+ onAccept={() => track('consent_accepted')}
246
+ onDecline={() => track('consent_declined')}
247
+ position="bottom" // or "top"
248
+ theme="light" // "light" | "dark" | "auto"
249
+ className="my-extra-classes"
250
+ style={{ background: '#fafafa' }} // wins over the theme defaults
251
+ />
252
+ ```
253
+
254
+ ### Fully custom UI — `useConsent()`
255
+
256
+ For a bespoke banner, skip the component and drive your own UI with the headless hook:
257
+
258
+ ```tsx
259
+ 'use client';
260
+ import { useConsent } from '@aranova/tracking-next';
261
+
262
+ function CookieBar() {
263
+ const { state, accept, decline, reset, isPending } = useConsent();
264
+
265
+ if (!isPending) {
266
+ // Footer link: re-open the banner if they change their mind.
267
+ return <button onClick={reset}>Cookie preferences</button>;
268
+ }
269
+ return (
270
+ <MyBespokeBanner>
271
+ <button onClick={decline}>No thanks</button>
272
+ <button onClick={accept}>Sure</button>
273
+ </MyBespokeBanner>
274
+ );
275
+ }
276
+ ```
277
+
278
+ 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.).
279
+
219
280
  ## Exports
220
281
 
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`)
282
+ - 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
283
  - `@aranova/tracking-next/middleware`: `createTrackingMiddleware()`
223
284
  - `@aranova/tracking-next/server`: `getTrackingParamsServer()`
224
285
  - 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;
@@ -1487,6 +1503,61 @@ interface SaleListQuery extends SaleFilters {
1487
1503
  limit?: number;
1488
1504
  cursor?: string | null;
1489
1505
  }
1506
+ declare const TRACKING_RANGES: readonly ["24h", "7d", "30d"];
1507
+ type TrackingOverviewRange = (typeof TRACKING_RANGES)[number];
1508
+ /** Query input for `SalesClient.summary()` — filters + range + options. */
1509
+ interface SaleSummaryQuery extends SaleFilters {
1510
+ range: TrackingOverviewRange;
1511
+ /** Include the per-category line-item breakdown (extra join; default false). */
1512
+ include_categories?: boolean;
1513
+ /** Surface soft-deleted services individually (flagged "(deleted)") instead of
1514
+ * rolling them into a single "Deleted services" bucket. Default false. */
1515
+ include_deleted_services?: boolean;
1516
+ /** Cap on the by-service / by-category rows (1–50, default 10). */
1517
+ top_n?: number;
1518
+ }
1519
+ interface CurrencyRevenue {
1520
+ currency: SupportedCurrency;
1521
+ sale_count: number;
1522
+ revenue_cents: number;
1523
+ average_order_value_cents: number;
1524
+ }
1525
+ interface SalesServiceBreakdown {
1526
+ service_id: string | null;
1527
+ service_key: string | null;
1528
+ /** "Unassigned" when the sale has no service. */
1529
+ service_label: string | null;
1530
+ currency: SupportedCurrency;
1531
+ sale_count: number;
1532
+ revenue_cents: number;
1533
+ }
1534
+ interface SalesCategoryBreakdown {
1535
+ category: string | null;
1536
+ currency: SupportedCurrency;
1537
+ /** sum(unit_price_cents * quantity) — advisory, not authoritative. */
1538
+ revenue_cents: number;
1539
+ /** Decimal string. */
1540
+ quantity: string;
1541
+ }
1542
+ interface SalesTrendPoint {
1543
+ bucket_start: string;
1544
+ currency: SupportedCurrency;
1545
+ sale_count: number;
1546
+ revenue_cents: number;
1547
+ }
1548
+ interface SaleSummary {
1549
+ range: TrackingOverviewRange;
1550
+ business_id: string | null;
1551
+ metrics: {
1552
+ sale_count: number;
1553
+ distinct_customers: number;
1554
+ by_currency: CurrencyRevenue[];
1555
+ };
1556
+ by_service: SalesServiceBreakdown[];
1557
+ by_currency: CurrencyRevenue[];
1558
+ by_category: SalesCategoryBreakdown[];
1559
+ series: SalesTrendPoint[];
1560
+ }
1490
1561
 
1491
1562
  /** Shared config for every sales HTTP helper. */
1492
1563
  interface SalesTransportConfig {
@@ -1526,6 +1597,12 @@ interface SalesClient<TService extends string = string> {
1526
1597
  occurred_at?: string;
1527
1598
  }): Promise<Sale>;
1528
1599
  list(query?: SaleListQuery): Promise<SaleCursorPage>;
1600
+ /**
1601
+ * Currency-grouped aggregations for the key's business (revenue per currency,
1602
+ * AOV, distinct customers, by-service / by-currency / by-category breakdowns,
1603
+ * and a revenue/count trend). Secret key only — a public key gets a `403`.
1604
+ */
1605
+ summary(query: SaleSummaryQuery): Promise<SaleSummary>;
1529
1606
  get(id: string): Promise<Sale>;
1530
1607
  update(id: string, patch: Omit<SaleUpdateInput, 'service'> & {
1531
1608
  service?: TService | null;
@@ -1580,12 +1657,69 @@ declare class AranovaApiError extends Error {
1580
1657
  }
1581
1658
 
1582
1659
  /**
1583
- * Default non-blocking consent banner for Next.js installs.
1660
+ * Default non-blocking consent banner.
1661
+ *
1662
+ * Renders only while consent is `pending`; collapses to `null` once the
1663
+ * visitor has chosen.
1664
+ *
1665
+ * **Styling is intentionally self-contained** — inline styles, zero CSS
1666
+ * dependencies, no Tailwind required at the consumer. The Tailwind-based
1667
+ * banner shipped before 0.9.1 rendered as transparent in any consumer that
1668
+ * didn't configure their content array to scan
1669
+ * `node_modules/@aranova/tracking-next/dist/**`; this version sidesteps that
1670
+ * class of bug entirely.
1671
+ *
1672
+ * For a fully bespoke banner, skip this component and use {@link useConsent}
1673
+ * directly to drive your own UI.
1674
+ *
1675
+ * @example
1676
+ * // Drop-in default
1677
+ * <ConsentBanner />
1584
1678
  *
1585
- * Renders only while consent is `pending`. Accept/decline choices are stored
1586
- * in localStorage and propagated to Google Consent Mode when gtag is loaded.
1679
+ * @example
1680
+ * // Customized
1681
+ * <ConsentBanner
1682
+ * message="We use cookies to learn which ads drive bookings."
1683
+ * acceptLabel="Sounds good"
1684
+ * declineLabel="No thanks"
1685
+ * policyHref="/privacy"
1686
+ * policyLabel="Privacy policy"
1687
+ * theme="dark"
1688
+ * onAccept={() => track('consent_accepted')}
1689
+ * onDecline={() => track('consent_declined')}
1690
+ * />
1587
1691
  */
1588
- declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
1692
+ interface ConsentBannerProps {
1693
+ /** Body text. Defaults to the standard cookies-for-ad-performance message. */
1694
+ message?: ReactNode;
1695
+ /** Optional bold title above the body text. */
1696
+ title?: ReactNode;
1697
+ /** Label for the accept button. Default: `"Accept"`. */
1698
+ acceptLabel?: string;
1699
+ /** Label for the decline button. Default: `"Decline"`. */
1700
+ declineLabel?: string;
1701
+ /** Optional link inline with the message (e.g. to a privacy policy). */
1702
+ policyHref?: string;
1703
+ /** Visible text for {@link policyHref}. Default: `"Learn more"`. */
1704
+ policyLabel?: string;
1705
+ /**
1706
+ * Fires after the consent state is persisted + propagated to gtag. Useful
1707
+ * for emitting your own analytics event on the choice.
1708
+ */
1709
+ onAccept?: () => void;
1710
+ onDecline?: () => void;
1711
+ /** Where the banner docks. Default: `"bottom"`. */
1712
+ position?: 'top' | 'bottom';
1713
+ /**
1714
+ * Visual theme. `"auto"` follows `prefers-color-scheme`. Default: `"light"`.
1715
+ */
1716
+ theme?: 'light' | 'dark' | 'auto';
1717
+ /** Class added to the outer wrapper for additional styling hooks. */
1718
+ className?: string;
1719
+ /** Inline style overrides applied to the outer wrapper after the defaults. */
1720
+ style?: CSSProperties;
1721
+ }
1722
+ declare function ConsentBanner({ message, title, acceptLabel, declineLabel, policyHref, policyLabel, onAccept, onDecline, position, theme, className, style, }?: ConsentBannerProps): ReactNode;
1589
1723
 
1590
1724
  /**
1591
1725
  * Read the captured Google Ads click id from first-party cookies.
@@ -1602,8 +1736,46 @@ declare function useTrackingParams(): TrackingParams;
1602
1736
  /**
1603
1737
  * Read the current visitor consent state and update when another tab changes
1604
1738
  * the stored value.
1739
+ *
1740
+ * Prefer {@link useConsent} for new code — it returns the same state plus
1741
+ * the `accept` / `decline` / `reset` actions a custom consent UI needs.
1742
+ * `useConsentState` is kept as a convenience for callers that only need to
1743
+ * read.
1605
1744
  */
1606
1745
  declare function useConsentState(): ConsentState;
1746
+ /**
1747
+ * The headless consent surface — state + actions in one hook.
1748
+ *
1749
+ * Build a fully-custom banner without losing the gtag-sync, localStorage
1750
+ * persistence, or cross-tab propagation:
1751
+ *
1752
+ * ```tsx
1753
+ * const { state, accept, decline, reset, isPending } = useConsent();
1754
+ *
1755
+ * if (!isPending) {
1756
+ * return <button onClick={reset}>Cookie preferences</button>;
1757
+ * }
1758
+ * return (
1759
+ * <MyBannerStyling>
1760
+ * <button onClick={decline}>No thanks</button>
1761
+ * <button onClick={accept}>Sure</button>
1762
+ * </MyBannerStyling>
1763
+ * );
1764
+ * ```
1765
+ *
1766
+ * The boolean helpers (`isPending` / `isGranted` / `isDenied`) are equivalent
1767
+ * to comparing `state` directly — they're there for readability at call sites.
1768
+ */
1769
+ interface UseConsentResult {
1770
+ state: ConsentState;
1771
+ isPending: boolean;
1772
+ isGranted: boolean;
1773
+ isDenied: boolean;
1774
+ accept: () => void;
1775
+ decline: () => void;
1776
+ reset: () => void;
1777
+ }
1778
+ declare function useConsent(): UseConsentResult;
1607
1779
 
1608
1780
  /**
1609
1781
  * Props for the Next.js Google Ads tracking component.
@@ -1692,4 +1864,4 @@ interface CreateTrackingResult<TRegistry extends TriggerRegistryConfig> {
1692
1864
  */
1693
1865
  declare function createTracking<TRegistry extends TriggerRegistryConfig>(options: CreateTrackingOptions<TRegistry>): CreateTrackingResult<TRegistry>;
1694
1866
 
1695
- 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 };
1867
+ export { AranovaApiError, type AutomaticEventName, ConsentBanner, type ConsentBannerProps, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type CurrencyRevenue, 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 SaleSummary, type SaleSummaryQuery, type SaleUpdateInput, type SalesCategoryBreakdown, type SalesClient, type SalesClientConfig, type SalesServiceBreakdown, type SalesTransportConfig, type SalesTrendPoint, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, type SupportedCurrency, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, type TrackingOverviewRange, 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;
@@ -1487,6 +1503,61 @@ interface SaleListQuery extends SaleFilters {
1487
1503
  limit?: number;
1488
1504
  cursor?: string | null;
1489
1505
  }
1506
+ declare const TRACKING_RANGES: readonly ["24h", "7d", "30d"];
1507
+ type TrackingOverviewRange = (typeof TRACKING_RANGES)[number];
1508
+ /** Query input for `SalesClient.summary()` — filters + range + options. */
1509
+ interface SaleSummaryQuery extends SaleFilters {
1510
+ range: TrackingOverviewRange;
1511
+ /** Include the per-category line-item breakdown (extra join; default false). */
1512
+ include_categories?: boolean;
1513
+ /** Surface soft-deleted services individually (flagged "(deleted)") instead of
1514
+ * rolling them into a single "Deleted services" bucket. Default false. */
1515
+ include_deleted_services?: boolean;
1516
+ /** Cap on the by-service / by-category rows (1–50, default 10). */
1517
+ top_n?: number;
1518
+ }
1519
+ interface CurrencyRevenue {
1520
+ currency: SupportedCurrency;
1521
+ sale_count: number;
1522
+ revenue_cents: number;
1523
+ average_order_value_cents: number;
1524
+ }
1525
+ interface SalesServiceBreakdown {
1526
+ service_id: string | null;
1527
+ service_key: string | null;
1528
+ /** "Unassigned" when the sale has no service. */
1529
+ service_label: string | null;
1530
+ currency: SupportedCurrency;
1531
+ sale_count: number;
1532
+ revenue_cents: number;
1533
+ }
1534
+ interface SalesCategoryBreakdown {
1535
+ category: string | null;
1536
+ currency: SupportedCurrency;
1537
+ /** sum(unit_price_cents * quantity) — advisory, not authoritative. */
1538
+ revenue_cents: number;
1539
+ /** Decimal string. */
1540
+ quantity: string;
1541
+ }
1542
+ interface SalesTrendPoint {
1543
+ bucket_start: string;
1544
+ currency: SupportedCurrency;
1545
+ sale_count: number;
1546
+ revenue_cents: number;
1547
+ }
1548
+ interface SaleSummary {
1549
+ range: TrackingOverviewRange;
1550
+ business_id: string | null;
1551
+ metrics: {
1552
+ sale_count: number;
1553
+ distinct_customers: number;
1554
+ by_currency: CurrencyRevenue[];
1555
+ };
1556
+ by_service: SalesServiceBreakdown[];
1557
+ by_currency: CurrencyRevenue[];
1558
+ by_category: SalesCategoryBreakdown[];
1559
+ series: SalesTrendPoint[];
1560
+ }
1490
1561
 
1491
1562
  /** Shared config for every sales HTTP helper. */
1492
1563
  interface SalesTransportConfig {
@@ -1526,6 +1597,12 @@ interface SalesClient<TService extends string = string> {
1526
1597
  occurred_at?: string;
1527
1598
  }): Promise<Sale>;
1528
1599
  list(query?: SaleListQuery): Promise<SaleCursorPage>;
1600
+ /**
1601
+ * Currency-grouped aggregations for the key's business (revenue per currency,
1602
+ * AOV, distinct customers, by-service / by-currency / by-category breakdowns,
1603
+ * and a revenue/count trend). Secret key only — a public key gets a `403`.
1604
+ */
1605
+ summary(query: SaleSummaryQuery): Promise<SaleSummary>;
1529
1606
  get(id: string): Promise<Sale>;
1530
1607
  update(id: string, patch: Omit<SaleUpdateInput, 'service'> & {
1531
1608
  service?: TService | null;
@@ -1580,12 +1657,69 @@ declare class AranovaApiError extends Error {
1580
1657
  }
1581
1658
 
1582
1659
  /**
1583
- * Default non-blocking consent banner for Next.js installs.
1660
+ * Default non-blocking consent banner.
1661
+ *
1662
+ * Renders only while consent is `pending`; collapses to `null` once the
1663
+ * visitor has chosen.
1664
+ *
1665
+ * **Styling is intentionally self-contained** — inline styles, zero CSS
1666
+ * dependencies, no Tailwind required at the consumer. The Tailwind-based
1667
+ * banner shipped before 0.9.1 rendered as transparent in any consumer that
1668
+ * didn't configure their content array to scan
1669
+ * `node_modules/@aranova/tracking-next/dist/**`; this version sidesteps that
1670
+ * class of bug entirely.
1671
+ *
1672
+ * For a fully bespoke banner, skip this component and use {@link useConsent}
1673
+ * directly to drive your own UI.
1674
+ *
1675
+ * @example
1676
+ * // Drop-in default
1677
+ * <ConsentBanner />
1584
1678
  *
1585
- * Renders only while consent is `pending`. Accept/decline choices are stored
1586
- * in localStorage and propagated to Google Consent Mode when gtag is loaded.
1679
+ * @example
1680
+ * // Customized
1681
+ * <ConsentBanner
1682
+ * message="We use cookies to learn which ads drive bookings."
1683
+ * acceptLabel="Sounds good"
1684
+ * declineLabel="No thanks"
1685
+ * policyHref="/privacy"
1686
+ * policyLabel="Privacy policy"
1687
+ * theme="dark"
1688
+ * onAccept={() => track('consent_accepted')}
1689
+ * onDecline={() => track('consent_declined')}
1690
+ * />
1587
1691
  */
1588
- declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
1692
+ interface ConsentBannerProps {
1693
+ /** Body text. Defaults to the standard cookies-for-ad-performance message. */
1694
+ message?: ReactNode;
1695
+ /** Optional bold title above the body text. */
1696
+ title?: ReactNode;
1697
+ /** Label for the accept button. Default: `"Accept"`. */
1698
+ acceptLabel?: string;
1699
+ /** Label for the decline button. Default: `"Decline"`. */
1700
+ declineLabel?: string;
1701
+ /** Optional link inline with the message (e.g. to a privacy policy). */
1702
+ policyHref?: string;
1703
+ /** Visible text for {@link policyHref}. Default: `"Learn more"`. */
1704
+ policyLabel?: string;
1705
+ /**
1706
+ * Fires after the consent state is persisted + propagated to gtag. Useful
1707
+ * for emitting your own analytics event on the choice.
1708
+ */
1709
+ onAccept?: () => void;
1710
+ onDecline?: () => void;
1711
+ /** Where the banner docks. Default: `"bottom"`. */
1712
+ position?: 'top' | 'bottom';
1713
+ /**
1714
+ * Visual theme. `"auto"` follows `prefers-color-scheme`. Default: `"light"`.
1715
+ */
1716
+ theme?: 'light' | 'dark' | 'auto';
1717
+ /** Class added to the outer wrapper for additional styling hooks. */
1718
+ className?: string;
1719
+ /** Inline style overrides applied to the outer wrapper after the defaults. */
1720
+ style?: CSSProperties;
1721
+ }
1722
+ declare function ConsentBanner({ message, title, acceptLabel, declineLabel, policyHref, policyLabel, onAccept, onDecline, position, theme, className, style, }?: ConsentBannerProps): ReactNode;
1589
1723
 
1590
1724
  /**
1591
1725
  * Read the captured Google Ads click id from first-party cookies.
@@ -1602,8 +1736,46 @@ declare function useTrackingParams(): TrackingParams;
1602
1736
  /**
1603
1737
  * Read the current visitor consent state and update when another tab changes
1604
1738
  * the stored value.
1739
+ *
1740
+ * Prefer {@link useConsent} for new code — it returns the same state plus
1741
+ * the `accept` / `decline` / `reset` actions a custom consent UI needs.
1742
+ * `useConsentState` is kept as a convenience for callers that only need to
1743
+ * read.
1605
1744
  */
1606
1745
  declare function useConsentState(): ConsentState;
1746
+ /**
1747
+ * The headless consent surface — state + actions in one hook.
1748
+ *
1749
+ * Build a fully-custom banner without losing the gtag-sync, localStorage
1750
+ * persistence, or cross-tab propagation:
1751
+ *
1752
+ * ```tsx
1753
+ * const { state, accept, decline, reset, isPending } = useConsent();
1754
+ *
1755
+ * if (!isPending) {
1756
+ * return <button onClick={reset}>Cookie preferences</button>;
1757
+ * }
1758
+ * return (
1759
+ * <MyBannerStyling>
1760
+ * <button onClick={decline}>No thanks</button>
1761
+ * <button onClick={accept}>Sure</button>
1762
+ * </MyBannerStyling>
1763
+ * );
1764
+ * ```
1765
+ *
1766
+ * The boolean helpers (`isPending` / `isGranted` / `isDenied`) are equivalent
1767
+ * to comparing `state` directly — they're there for readability at call sites.
1768
+ */
1769
+ interface UseConsentResult {
1770
+ state: ConsentState;
1771
+ isPending: boolean;
1772
+ isGranted: boolean;
1773
+ isDenied: boolean;
1774
+ accept: () => void;
1775
+ decline: () => void;
1776
+ reset: () => void;
1777
+ }
1778
+ declare function useConsent(): UseConsentResult;
1607
1779
 
1608
1780
  /**
1609
1781
  * Props for the Next.js Google Ads tracking component.
@@ -1692,4 +1864,4 @@ interface CreateTrackingResult<TRegistry extends TriggerRegistryConfig> {
1692
1864
  */
1693
1865
  declare function createTracking<TRegistry extends TriggerRegistryConfig>(options: CreateTrackingOptions<TRegistry>): CreateTrackingResult<TRegistry>;
1694
1866
 
1695
- 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 };
1867
+ export { AranovaApiError, type AutomaticEventName, ConsentBanner, type ConsentBannerProps, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type CurrencyRevenue, 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 SaleSummary, type SaleSummaryQuery, type SaleUpdateInput, type SalesCategoryBreakdown, type SalesClient, type SalesClientConfig, type SalesServiceBreakdown, type SalesTransportConfig, type SalesTrendPoint, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, type SupportedCurrency, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, type TrackingOverviewRange, 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 };