@aranova/tracking-next 0.11.0 → 0.12.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
@@ -14,12 +14,12 @@ Install the tracking middleware so `gclid`, `fbclid`, and UTM params are written
14
14
 
15
15
  ```ts
16
16
  // middleware.ts
17
- import { createTrackingMiddleware } from '@aranova/tracking-next/middleware';
17
+ import { createTrackingMiddleware } from "@aranova/tracking-next/middleware";
18
18
 
19
19
  export default createTrackingMiddleware();
20
20
 
21
21
  export const config = {
22
- matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
22
+ matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
23
23
  };
24
24
  ```
25
25
 
@@ -29,7 +29,7 @@ Create one shared tracking module and use its scoped exports throughout your app
29
29
 
30
30
  ```ts
31
31
  // lib/tracking.ts
32
- import { createTracking } from '@aranova/tracking-next';
32
+ import { createTracking } from "@aranova/tracking-next";
33
33
 
34
34
  export const { TrackingProvider, useTracking } = createTracking({
35
35
  apiKey: process.env.NEXT_PUBLIC_ARANOVA_TRACKING_API_KEY!,
@@ -39,7 +39,7 @@ export const { TrackingProvider, useTracking } = createTracking({
39
39
  page_view: {},
40
40
  time_on_site: { thresholdSeconds: 60 },
41
41
  specific_page_visit: {
42
- pages: [{ name: 'contact_page', pathPattern: /^\/(contact|book|get-started)/ }],
42
+ pages: [{ name: "contact_page", pathPattern: /^\/(contact|book|get-started)/ }],
43
43
  },
44
44
  },
45
45
  manual: {
@@ -48,7 +48,7 @@ export const { TrackingProvider, useTracking } = createTracking({
48
48
  cta_click: {},
49
49
  },
50
50
  },
51
- debug: process.env.NODE_ENV !== 'production',
51
+ debug: process.env.NODE_ENV !== "production",
52
52
  });
53
53
  ```
54
54
 
@@ -56,8 +56,8 @@ Wrap the root layout.
56
56
 
57
57
  ```tsx
58
58
  // app/layout.tsx
59
- import { ConsentBanner, GoogleAdsTracking } from '@aranova/tracking-next';
60
- import { TrackingProvider } from '@/lib/tracking';
59
+ import { ConsentBanner, GoogleAdsTracking } from "@aranova/tracking-next";
60
+ import { TrackingProvider } from "@/lib/tracking";
61
61
 
62
62
  export default function RootLayout({ children }: { children: React.ReactNode }) {
63
63
  return (
@@ -80,31 +80,12 @@ export default function RootLayout({ children }: { children: React.ReactNode })
80
80
 
81
81
  ### Multiple gtag IDs
82
82
 
83
- To install multiple Google Ads tags simultaneously (for example a production MCC and a test MCC for verifying conversion actions before they touch the live account), pass `gtagIds` instead of `gtagId`. Every entry fires `gtag('config', ...)` on every page — gtag natively supports multiple configured tags.
83
+ Pass `gtagIds` (instead of `gtagId`) to install several Google Ads tags at once — e.g. a real
84
+ account plus a test MCC. Each entry fires `gtag('config', …)` on every page; the labels surface in
85
+ the dashboard's SDK table. Pass `environment` to the factory to tag events for dashboard filtering.
84
86
 
85
87
  ```tsx
86
- <GoogleAdsTracking
87
- gtagIds={{
88
- production: 'AW-111111111', // real client account
89
- test: 'AW-222222222', // test MCC for development
90
- }}
91
- />
92
- ```
93
-
94
- The labels are arbitrary and surface in the Aranova dashboard's SDK versions table. Also pass `environment` to the factory so events are tagged with the deployment context for dashboard filtering:
95
-
96
- ```ts
97
- // lib/tracking.ts
98
- createTracking({
99
- apiKey: process.env.NEXT_PUBLIC_ARANOVA_TRACKING_API_KEY!,
100
- endpoint: process.env.NEXT_PUBLIC_ARANOVA_TRACKING_ENDPOINT!,
101
- environment: process.env.NEXT_PUBLIC_TRACKING_ENVIRONMENT, // 'production' | 'development'
102
- gtagIds: { // optional — reports the configured tags in heartbeat metadata
103
- production: process.env.NEXT_PUBLIC_GTAG_PROD!,
104
- test: process.env.NEXT_PUBLIC_GTAG_TEST!,
105
- },
106
- triggers: { /* ... */ },
107
- });
88
+ <GoogleAdsTracking gtagIds={{ production: "AW-111111111", test: "AW-222222222" }} />
108
89
  ```
109
90
 
110
91
  ## Manual Events
@@ -112,9 +93,9 @@ createTracking({
112
93
  Import `useTracking` from your local `lib/tracking` module, not directly from the package.
113
94
 
114
95
  ```tsx
115
- 'use client';
96
+ "use client";
116
97
 
117
- import { useTracking } from '@/lib/tracking';
98
+ import { useTracking } from "@/lib/tracking";
118
99
 
119
100
  export function LeadForm() {
120
101
  const tracking = useTracking();
@@ -127,22 +108,22 @@ export function LeadForm() {
127
108
  const form = event.currentTarget;
128
109
  const data = new FormData(form);
129
110
 
130
- tracking.trackEvent('form_submit', {
111
+ tracking.trackEvent("form_submit", {
131
112
  form: {
132
113
  id: form.id,
133
- action: form.getAttribute('action'),
114
+ action: form.getAttribute("action"),
134
115
  fields: [
135
116
  {
136
- name: 'service_interest',
137
- type: 'select',
138
- label: 'Service interest',
139
- value: String(data.get('service_interest') ?? ''),
117
+ name: "service_interest",
118
+ type: "select",
119
+ label: "Service interest",
120
+ value: String(data.get("service_interest") ?? ""),
140
121
  },
141
122
  {
142
- name: 'is_existing_patient',
143
- type: 'checkbox',
144
- label: 'Existing patient',
145
- value: data.get('is_existing_patient') === 'on',
123
+ name: "is_existing_patient",
124
+ type: "checkbox",
125
+ label: "Existing patient",
126
+ value: data.get("is_existing_patient") === "on",
146
127
  },
147
128
  ],
148
129
  },
@@ -158,16 +139,33 @@ export function LeadForm() {
158
139
 
159
140
  `fields[].value` can be any JSON value: string, number, boolean, null, array, or object. Only send reviewed, allowlisted, non-sensitive values; do not send names, emails, phone numbers entered by the visitor, addresses, payment data, medical details, passwords, file contents, or free-text messages.
160
141
 
142
+ ## Phone Fields
143
+
144
+ Bundled `libphonenumber-js`: parse/format utils + a React input. Display is configurable; the
145
+ value sent to the backend is **always E.164**. Configure once via `createTracking({ phone: { defaultCountry: 'CA', display: 'national' } })`.
146
+
147
+ ```tsx
148
+ "use client";
149
+ import { usePhoneField, PhoneField, toE164 } from "@aranova/tracking-next";
150
+
151
+ const phone = usePhoneField(); // phone.value (display), phone.e164 (wire), .isValid, .error
152
+ <input {...phone.inputProps} />; // or the batteries-included <PhoneField name="phone" />
153
+ ```
154
+
155
+ Pure, isomorphic utils (usable in Server Components / route handlers) are at
156
+ `@aranova/tracking-next/phone`. Full guide:
157
+ [phone.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/phone.md).
158
+
161
159
  ## Server Reads
162
160
 
163
161
  Read attribution cookies from Server Components or server actions.
164
162
 
165
163
  ```tsx
166
- import { getTrackingParamsServer } from '@aranova/tracking-next/server';
164
+ import { getTrackingParamsServer } from "@aranova/tracking-next/server";
167
165
 
168
166
  export default function Page() {
169
167
  const { gclid, utm_source } = getTrackingParamsServer();
170
- return gclid || utm_source === 'google' ? <GoogleLanding /> : <OrganicLanding />;
168
+ return gclid || utm_source === "google" ? <GoogleLanding /> : <OrganicLanding />;
171
169
  }
172
170
  ```
173
171
 
@@ -181,34 +179,43 @@ key may do is enforced by the backend, not by hiding methods. Browser write with
181
179
  your **public** key, from a client component:
182
180
 
183
181
  ```tsx
184
- 'use client';
185
- import { createSalesClient, toMinor } from '@aranova/tracking-next';
182
+ "use client";
183
+ import { createSalesClient, toMinor } from "@aranova/tracking-next";
186
184
 
187
- const sales = createSalesClient({ apiKey: process.env.NEXT_PUBLIC_ARANOVA_TRACKING_API_KEY!, endpoint });
188
- await sales.record({ currency: 'CAD', amount_total_cents: toMinor(250, 'CAD'), service: 'tires' });
185
+ const sales = createSalesClient({
186
+ apiKey: process.env.NEXT_PUBLIC_ARANOVA_TRACKING_API_KEY!,
187
+ endpoint,
188
+ });
189
+ await sales.record({ currency: "CAD", amount_total_cents: toMinor(250, "CAD"), service: "tires" });
189
190
  ```
190
191
 
191
- Full CRUD from a route handler / server action with your **secret** key (same
192
- import — keep the secret key in server env, never in client code):
192
+ Full CRUD from a route handler / server action / **Server Component** with your
193
+ **secret** key. Import from the **`/sales`** subpath — it's React-free, so it loads
194
+ in an RSC or any server module without dragging the provider/components into the
195
+ server graph (keep the secret key in server env, never in client code):
193
196
 
194
197
  ```ts
195
- import { createSalesClient } from '@aranova/tracking-next';
196
- import type { AranovaService } from './aranova-services'; // generated, see below
198
+ import { createSalesClient } from "@aranova/tracking-next/sales";
199
+ import type { AranovaService } from "./aranova-services"; // generated, see below
197
200
 
198
201
  const sales = createSalesClient<AranovaService>({
199
202
  apiKey: process.env.ARANOVA_TRACKING_SECRET_KEY!,
200
203
  endpoint: process.env.ARANOVA_TRACKING_ENDPOINT!,
201
204
  });
202
- const { items, next_cursor } = await sales.list({ limit: 50 });
203
- const summary = await sales.summary({ range: '30d' }); // currency-grouped aggregations
205
+ await sales.list({ sort: "amount_total_cents", want_total: true }); // → { items, total_count, … }
206
+ await sales.summary({ range: "mtd", timezone: "America/Toronto", compare_to: "previous_period" });
207
+ await sales.customers.list({ segment: "returning", sort: "total_spent" }); // phone-keyed roster
208
+ const cfg = await sales.business.config(); // tz / currencies / services
204
209
  ```
205
210
 
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.
211
+ The root entry (`@aranova/tracking-next`) still re-exports `createSalesClient` and the
212
+ money/date helpers for back-compat, so existing imports keep working — but prefer
213
+ `/sales` in server code so the React surface never reaches your server bundle.
209
214
 
210
- A public-key client calling `list`/`summary`/`get`/`update`/`delete` gets a `403`
211
- telling it to use a secret key server-side.
215
+ Dashboard reads are all currency-grouped (never summed). `summary()` gains tz-aware calendar/custom
216
+ ranges, `granularity`, and period-over-period `compare_to` (legacy `24h/7d/30d` unchanged); a
217
+ **customer is their phone (E.164)**, so `customers.*` is a live roster with no separate table.
218
+ A public-key client calling any read gets a `403`.
212
219
 
213
220
  Generate the typed `AranovaService` union from your dashboard services with the CLI
214
221
  (install it as a **devDependency**):
@@ -226,10 +233,10 @@ and the CLI reference: [cli.md](https://github.com/AranovaIO/aranova_internal/bl
226
233
  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
234
 
228
235
  ```tsx
229
- import { ConsentBanner } from '@aranova/tracking-next';
236
+ import { ConsentBanner } from "@aranova/tracking-next";
230
237
 
231
238
  // Drop-in (already wired in the root layout example above)
232
- <ConsentBanner />
239
+ <ConsentBanner />;
233
240
  ```
234
241
 
235
242
  All props are optional:
@@ -241,13 +248,13 @@ All props are optional:
241
248
  acceptLabel="Sure"
242
249
  declineLabel="No thanks"
243
250
  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"
251
+ policyLabel="Privacy policy" // default: "Learn more"
252
+ onAccept={() => track("consent_accepted")}
253
+ onDecline={() => track("consent_declined")}
254
+ position="bottom" // or "top"
255
+ theme="light" // "light" | "dark" | "auto"
249
256
  className="my-extra-classes"
250
- style={{ background: '#fafafa' }} // wins over the theme defaults
257
+ style={{ background: "#fafafa" }} // wins over the theme defaults
251
258
  />
252
259
  ```
253
260
 
@@ -256,8 +263,8 @@ All props are optional:
256
263
  For a bespoke banner, skip the component and drive your own UI with the headless hook:
257
264
 
258
265
  ```tsx
259
- 'use client';
260
- import { useConsent } from '@aranova/tracking-next';
266
+ "use client";
267
+ import { useConsent } from "@aranova/tracking-next";
261
268
 
262
269
  function CookieBar() {
263
270
  const { state, accept, decline, reset, isPending } = useConsent();
@@ -279,8 +286,9 @@ The hook handles localStorage persistence, gtag sync, and cross-tab propagation
279
286
 
280
287
  ## Exports
281
288
 
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`)
289
+ - Root package: `createTracking`, `TrackingProvider`, `useTracking`, `GoogleAdsTracking`, `ConsentBanner` (+ `ConsentBannerProps`), consent hooks/helpers, attribution hooks, event types; `createSalesClient()` (isomorphic — public key writes; secret key reads/CRUD, `summary`, `customers.*`, `business.config`) + `toMinor`/`fromMinor`/`formatMoney`/`formatDateInTz`; phone (`parsePhone`/`toE164`/`formatPhone`/`phoneField`, `usePhoneField`, `PhoneField`)
290
+ - `@aranova/tracking-next/sales`: **React-free** server-safe SDK — `createSalesClient`, money/date helpers, and all sale/customer/config types. Use this in Server Components, route handlers, and Node servers; the root entry re-exports the same symbols for back-compat.
283
291
  - `@aranova/tracking-next/middleware`: `createTrackingMiddleware()`
284
292
  - `@aranova/tracking-next/server`: `getTrackingParamsServer()`
293
+ - `@aranova/tracking-next/phone`: isomorphic phone utils (no React)
285
294
  - Codegen: [`@aranova/tracking-cli`](https://www.npmjs.com/package/@aranova/tracking-cli) — `gen` typed service unions (devDependency)
286
-