@aranova/tracking-react 0.12.0 → 0.12.2

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,7 +14,7 @@ Create one shared tracking module and import the scoped `TrackingProvider` / `us
14
14
 
15
15
  ```ts
16
16
  // src/lib/tracking.ts
17
- import { createTracking } from '@aranova/tracking-react';
17
+ import { createTracking } from "@aranova/tracking-react";
18
18
 
19
19
  export const { TrackingProvider, useTracking } = createTracking({
20
20
  apiKey: import.meta.env.VITE_ARANOVA_TRACKING_API_KEY,
@@ -24,7 +24,7 @@ export const { TrackingProvider, useTracking } = createTracking({
24
24
  page_view: {},
25
25
  time_on_site: { thresholdSeconds: 60 },
26
26
  specific_page_visit: {
27
- pages: [{ name: 'contact_page', pathPattern: /^\/(contact|book|get-started)/ }],
27
+ pages: [{ name: "contact_page", pathPattern: /^\/(contact|book|get-started)/ }],
28
28
  },
29
29
  },
30
30
  manual: {
@@ -33,7 +33,7 @@ export const { TrackingProvider, useTracking } = createTracking({
33
33
  cta_click: {},
34
34
  },
35
35
  },
36
- debug: import.meta.env.MODE !== 'production',
36
+ debug: import.meta.env.MODE !== "production",
37
37
  });
38
38
  ```
39
39
 
@@ -41,11 +41,11 @@ Mount the provider at the root of your React tree.
41
41
 
42
42
  ```tsx
43
43
  // src/main.tsx
44
- import { createRoot } from 'react-dom/client';
45
- import { App } from './App';
46
- import { TrackingProvider } from './lib/tracking';
44
+ import { createRoot } from "react-dom/client";
45
+ import { App } from "./App";
46
+ import { TrackingProvider } from "./lib/tracking";
47
47
 
48
- createRoot(document.getElementById('root')!).render(
48
+ createRoot(document.getElementById("root")!).render(
49
49
  <TrackingProvider gtagId={import.meta.env.VITE_GTAG_ID}>
50
50
  <App />
51
51
  </TrackingProvider>,
@@ -59,7 +59,7 @@ account plus a test MCC. Each entry fires `gtag('config', …)` on every page; t
59
59
  the dashboard's SDK table. Stamp events with `environment` to filter out test traffic.
60
60
 
61
61
  ```tsx
62
- <TrackingProvider gtagIds={{ production: 'AW-111111111', test: 'AW-222222222' }}>
62
+ <TrackingProvider gtagIds={{ production: "AW-111111111", test: "AW-222222222" }}>
63
63
  <App />
64
64
  </TrackingProvider>
65
65
  ```
@@ -69,7 +69,7 @@ the dashboard's SDK table. Stamp events with `environment` to filter out test tr
69
69
  Manual events must be registered under `triggers.manual` before `trackEvent()` accepts them.
70
70
 
71
71
  ```tsx
72
- import { useTracking } from './lib/tracking';
72
+ import { useTracking } from "./lib/tracking";
73
73
 
74
74
  export function LeadForm() {
75
75
  const tracking = useTracking();
@@ -78,22 +78,22 @@ export function LeadForm() {
78
78
  const form = event.currentTarget;
79
79
  const data = new FormData(form);
80
80
 
81
- tracking.trackEvent('form_submit', {
81
+ tracking.trackEvent("form_submit", {
82
82
  form: {
83
83
  id: form.id,
84
- action: form.getAttribute('action'),
84
+ action: form.getAttribute("action"),
85
85
  fields: [
86
86
  {
87
- name: 'service_interest',
88
- type: 'select',
89
- label: 'Service interest',
90
- value: String(data.get('service_interest') ?? ''),
87
+ name: "service_interest",
88
+ type: "select",
89
+ label: "Service interest",
90
+ value: String(data.get("service_interest") ?? ""),
91
91
  },
92
92
  {
93
- name: 'is_existing_patient',
94
- type: 'checkbox',
95
- label: 'Existing patient',
96
- value: data.get('is_existing_patient') === 'on',
93
+ name: "is_existing_patient",
94
+ type: "checkbox",
95
+ label: "Existing patient",
96
+ value: data.get("is_existing_patient") === "on",
97
97
  },
98
98
  ],
99
99
  },
@@ -101,17 +101,21 @@ export function LeadForm() {
101
101
  });
102
102
  }
103
103
 
104
- return <form id="lead-form" action="/api/lead" onSubmit={handleSubmit}>{/* fields */}</form>;
104
+ return (
105
+ <form id="lead-form" action="/api/lead" onSubmit={handleSubmit}>
106
+ {/* fields */}
107
+ </form>
108
+ );
105
109
  }
106
110
  ```
107
111
 
108
112
  `fields[].value` can be any JSON value: string, number, boolean, null, array, or object. Values must be JSON-serializable because events are stored as JSONB. 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.
109
113
 
110
114
  ```tsx
111
- tracking.trackEvent('phone_click', {
112
- phone_number: '+14165550199',
115
+ tracking.trackEvent("phone_click", {
116
+ phone_number: "+14165550199",
113
117
  page: { path: window.location.pathname },
114
- section: 'header',
118
+ section: "header",
115
119
  });
116
120
  ```
117
121
 
@@ -121,12 +125,12 @@ Bundled `libphonenumber-js`: parse/format utils + a React input. Display is conf
121
125
  value sent to the backend is **always E.164**. Configure once via `createTracking({ phone: { defaultCountry: 'CA', display: 'national' } })`.
122
126
 
123
127
  ```tsx
124
- import { usePhoneField, PhoneField, toE164 } from '@aranova/tracking-react';
128
+ import { usePhoneField, PhoneField, toE164 } from "@aranova/tracking-react";
125
129
 
126
- const phone = usePhoneField(); // phone.value (display), phone.e164 (wire), .isValid, .error
127
- <input {...phone.inputProps} />; // or the batteries-included <PhoneField name="phone" />
130
+ const phone = usePhoneField(); // phone.value (display), phone.e164 (wire), .isValid, .error
131
+ <input {...phone.inputProps} />; // or the batteries-included <PhoneField name="phone" />
128
132
 
129
- toE164('416-555-0199'); // '+14165550199' (null if invalid)
133
+ toE164("416-555-0199"); // '+14165550199' (null if invalid)
130
134
  ```
131
135
 
132
136
  Pure, isomorphic utils are also at `@aranova/tracking-react/phone` (no React). Full guide:
@@ -142,19 +146,23 @@ key may do is enforced by the backend, not by hiding methods. Browser write with
142
146
  your **public** key:
143
147
 
144
148
  ```tsx
145
- import { createSalesClient, toMinor } from '@aranova/tracking-react';
149
+ import { createSalesClient, toMinor } from "@aranova/tracking-react";
146
150
 
147
- const sales = createSalesClient({ apiKey: import.meta.env.VITE_ARANOVA_TRACKING_API_KEY, endpoint });
148
- await sales.record({ currency: 'CAD', amount_total_cents: toMinor(250, 'CAD'), service: 'tires' });
151
+ const sales = createSalesClient({
152
+ apiKey: import.meta.env.VITE_ARANOVA_TRACKING_API_KEY,
153
+ endpoint,
154
+ });
155
+ await sales.record({ currency: "CAD", amount_total_cents: toMinor(250, "CAD"), service: "tires" });
149
156
  ```
150
157
 
151
158
  Reads / full CRUD require a **secret** key and must run server-side — never ship a
152
159
  secret key in the browser bundle. In a Vite + Vercel app, hold it in a serverless
153
- function (same import, secret key):
160
+ function. Import from the **`/sales`** subpath — it's React-free, so the server
161
+ bundle never pulls in the provider/components:
154
162
 
155
163
  ```ts
156
164
  // api/sales.ts (Vercel serverless function — runs on the server)
157
- import { createSalesClient } from '@aranova/tracking-react';
165
+ import { createSalesClient } from "@aranova/tracking-react/sales";
158
166
 
159
167
  const sales = createSalesClient({
160
168
  apiKey: process.env.ARANOVA_TRACKING_SECRET_KEY!,
@@ -168,17 +176,21 @@ export default async function handler(_req, res) {
168
176
  Secret-key reads power dashboards (all currency-grouped — never summed across currencies):
169
177
 
170
178
  ```ts
171
- await sales.summary({ range: 'mtd', timezone: 'America/Toronto', compare_to: 'previous_period' });
172
- await sales.list({ sort: 'amount_total_cents', want_total: true }); // → { items, total_count, … }
173
- await sales.customers.list({ segment: 'returning', sort: 'total_spent' }); // phone-keyed roster
174
- await sales.customers.summary({ range: 'mtd' });
175
- const cfg = await sales.business.config(); // tz / currencies / services (pk or sk)
179
+ await sales.summary({ range: "mtd", timezone: "America/Toronto", compare_to: "previous_period" });
180
+ await sales.list({ sort: "amount_total_cents", want_total: true }); // → { items, total_count, … }
181
+ await sales.customers.list({ segment: "returning", sort: "total_spent" }); // phone-keyed roster
182
+ await sales.customers.summary({ range: "mtd" });
183
+ const cfg = await sales.business.config(); // tz / currencies / services (pk or sk)
176
184
  ```
177
185
 
178
186
  `summary()` gains tz-aware calendar/custom ranges, `granularity`, and period-over-period
179
187
  `compare_to`; legacy `24h/7d/30d` are unchanged. A **customer is their phone (E.164)** — `customers.*`
180
188
  is a live roster, no separate table. A public-key client calling any read gets a `403`.
181
189
 
190
+ The root entry (`@aranova/tracking-react`) still re-exports `createSalesClient` and the
191
+ money/date helpers for back-compat, so existing imports keep working — but prefer `/sales`
192
+ in server code so the React surface never reaches your server bundle.
193
+
182
194
  Generate the typed `AranovaService` union from your dashboard services with the CLI
183
195
  (install it as a **devDependency**):
184
196
 
@@ -195,10 +207,10 @@ and the CLI reference: [cli.md](https://github.com/AranovaIO/aranova_internal/bl
195
207
  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.
196
208
 
197
209
  ```tsx
198
- import { ConsentBanner } from '@aranova/tracking-react';
210
+ import { ConsentBanner } from "@aranova/tracking-react";
199
211
 
200
212
  // Drop-in, defaults work everywhere
201
- <ConsentBanner />
213
+ <ConsentBanner />;
202
214
  ```
203
215
 
204
216
  All props are optional:
@@ -210,13 +222,13 @@ All props are optional:
210
222
  acceptLabel="Sure"
211
223
  declineLabel="No thanks"
212
224
  policyHref="/privacy"
213
- policyLabel="Privacy policy" // default: "Learn more"
214
- onAccept={() => track('consent_accepted')}
215
- onDecline={() => track('consent_declined')}
216
- position="bottom" // or "top"
217
- theme="light" // "light" | "dark" | "auto"
225
+ policyLabel="Privacy policy" // default: "Learn more"
226
+ onAccept={() => track("consent_accepted")}
227
+ onDecline={() => track("consent_declined")}
228
+ position="bottom" // or "top"
229
+ theme="light" // "light" | "dark" | "auto"
218
230
  className="my-extra-classes"
219
- style={{ background: '#fafafa' }} // wins over the theme defaults
231
+ style={{ background: "#fafafa" }} // wins over the theme defaults
220
232
  />
221
233
  ```
222
234
 
@@ -225,7 +237,7 @@ All props are optional:
225
237
  For a bespoke banner, skip the component and drive your own UI with the headless hook:
226
238
 
227
239
  ```tsx
228
- import { useConsent } from '@aranova/tracking-react';
240
+ import { useConsent } from "@aranova/tracking-react";
229
241
 
230
242
  function CookieBar() {
231
243
  const { state, accept, decline, reset, isPending } = useConsent();
@@ -254,8 +266,7 @@ The hook handles localStorage persistence, gtag sync, and cross-tab propagation
254
266
  - Consent hooks: `useConsent()` + `UseConsentResult` (headless); `useConsentState()` (read-only alias)
255
267
  - Standalone consent helpers: `getConsentState()`, `setConsentState()`, `resetConsent()`
256
268
  - Attribution hooks: `useTrackingParams()`, `useGclid()`
257
- - `createSalesClient()` (isomorphic — public key writes; secret key reads/CRUD, `summary`, `customers.*`, `business.config`) + money/date helpers (`toMinor`/`fromMinor`/`formatMoney`/`formatDateInTz`)
269
+ - `createSalesClient()` (isomorphic — public key writes; secret key reads/CRUD, `summary`, `customers.*`, `business.config`) + money/date helpers (`toMinor`/`fromMinor`/`formatMoney`/`formatDateInTz`). Also available React-free at **`@aranova/tracking-react/sales`** (with all sale/customer/config types) — the recommended import for server/serverless code.
258
270
  - Phone: `parsePhone`/`toE164`/`formatPhone`/`formatPhoneAsTyped`/`phoneField`, `usePhoneField`, `PhoneField` (utils also at `/phone`)
259
271
  - Codegen: [`@aranova/tracking-cli`](https://www.npmjs.com/package/@aranova/tracking-cli) — `gen` typed service unions (devDependency)
260
272
  - Event metadata/config types such as `FormSubmitMetadata`, `PhoneClickMetadata`, and `JsonValue`
261
-