@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/dist/index.mjs CHANGED
@@ -25,6 +25,12 @@ function setConsentState(state) {
25
25
  if (typeof window.gtag === "function")
26
26
  window.gtag("consent", "update", buildConsentPayload(state));
27
27
  }
28
+ function resetConsent() {
29
+ if (typeof window === "undefined")
30
+ return;
31
+ window.localStorage.removeItem(CONSENT_STATE_KEY);
32
+ window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
33
+ }
28
34
 
29
35
  // ../tracking-core/src/payloads.ts
30
36
  function createTrackingClientContext(surface, input = {}) {
@@ -1128,6 +1134,16 @@ function createSalesClient(config) {
1128
1134
  ...cursor !== void 0 ? { cursor } : {}
1129
1135
  });
1130
1136
  },
1137
+ async summary(query) {
1138
+ const { range, include_categories, include_deleted_services, top_n, ...filters } = query;
1139
+ return salesRequest(config, "POST", "/sales/summary", {
1140
+ filters,
1141
+ range,
1142
+ ...include_categories !== void 0 ? { include_categories } : {},
1143
+ ...include_deleted_services !== void 0 ? { include_deleted_services } : {},
1144
+ ...top_n !== void 0 ? { top_n } : {}
1145
+ });
1146
+ },
1131
1147
  async get(id) {
1132
1148
  return salesRequest(config, "GET", `/sales/${id}`);
1133
1149
  },
@@ -1166,92 +1182,255 @@ function formatMoney(cents, currency, locale) {
1166
1182
  }
1167
1183
 
1168
1184
  // src/ConsentBanner.tsx
1169
- import { useEffect, useState } from "react";
1170
- import { jsx, jsxs } from "react/jsx-runtime";
1171
- function ConsentBanner() {
1172
- const [consentState, setBannerConsentState] = useState("pending");
1173
- const [isReady, setIsReady] = useState(false);
1174
- useEffect(() => {
1175
- setBannerConsentState(getConsentState());
1176
- setIsReady(true);
1177
- }, []);
1178
- if (!isReady || consentState !== "pending")
1179
- return null;
1180
- const handleAccept = () => {
1181
- setConsentState("granted");
1182
- setBannerConsentState("granted");
1183
- };
1184
- const handleDecline = () => {
1185
- setConsentState("denied");
1186
- setBannerConsentState("denied");
1187
- };
1188
- return /* @__PURE__ */ jsx("div", { className: "fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between", children: [
1189
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-700", children: "We use cookies to understand ad performance and improve how our marketing works across visits. You can accept or decline this tracking." }),
1190
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
1191
- /* @__PURE__ */ jsx(
1192
- "button",
1193
- {
1194
- type: "button",
1195
- onClick: handleDecline,
1196
- className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100",
1197
- children: "Decline"
1198
- }
1199
- ),
1200
- /* @__PURE__ */ jsx(
1201
- "button",
1202
- {
1203
- type: "button",
1204
- onClick: handleAccept,
1205
- className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800",
1206
- children: "Accept"
1207
- }
1208
- )
1209
- ] })
1210
- ] }) });
1211
- }
1185
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1212
1186
 
1213
1187
  // src/hooks.ts
1214
- import { useEffect as useEffect2, useState as useState2 } from "react";
1188
+ import { useCallback, useEffect, useState } from "react";
1215
1189
  function useGclid() {
1216
- const [gclid, setGclid] = useState2(null);
1217
- useEffect2(() => {
1190
+ const [gclid, setGclid] = useState(null);
1191
+ useEffect(() => {
1218
1192
  setGclid(getCookieValueFromDocument("gclid"));
1219
1193
  }, []);
1220
1194
  return gclid;
1221
1195
  }
1222
1196
  function useTrackingParams() {
1223
- const [trackingParams, setTrackingParams] = useState2(createEmptyTrackingParams());
1224
- useEffect2(() => {
1197
+ const [trackingParams, setTrackingParams] = useState(createEmptyTrackingParams());
1198
+ useEffect(() => {
1225
1199
  setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
1226
1200
  }, []);
1227
1201
  return trackingParams;
1228
1202
  }
1229
1203
  function useConsentState() {
1230
- const [consentState, setConsentStateValue] = useState2("pending");
1231
- useEffect2(() => {
1232
- setConsentStateValue(getConsentState());
1204
+ return useConsent().state;
1205
+ }
1206
+ function useConsent() {
1207
+ const [state, setState] = useState("pending");
1208
+ useEffect(() => {
1209
+ setState(getConsentState());
1233
1210
  const handleStorage = (event) => {
1234
- if (event.key === "consent_state")
1235
- setConsentStateValue(getConsentState());
1211
+ if (event.key === CONSENT_STATE_KEY) setState(getConsentState());
1236
1212
  };
1237
1213
  window.addEventListener("storage", handleStorage);
1238
- return () => {
1239
- window.removeEventListener("storage", handleStorage);
1240
- };
1214
+ return () => window.removeEventListener("storage", handleStorage);
1215
+ }, []);
1216
+ const accept = useCallback(() => {
1217
+ setConsentState("granted");
1218
+ setState("granted");
1241
1219
  }, []);
1242
- return consentState;
1220
+ const decline = useCallback(() => {
1221
+ setConsentState("denied");
1222
+ setState("denied");
1223
+ }, []);
1224
+ const reset = useCallback(() => {
1225
+ resetConsent();
1226
+ setState("pending");
1227
+ }, []);
1228
+ return {
1229
+ state,
1230
+ isPending: state === "pending",
1231
+ isGranted: state === "granted",
1232
+ isDenied: state === "denied",
1233
+ accept,
1234
+ decline,
1235
+ reset
1236
+ };
1237
+ }
1238
+
1239
+ // src/ConsentBanner.tsx
1240
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1241
+ var LIGHT_THEME = {
1242
+ background: "#ffffff",
1243
+ border: "#e5e7eb",
1244
+ text: "#111827",
1245
+ mutedText: "#4b5563",
1246
+ acceptBg: "#111827",
1247
+ acceptText: "#ffffff",
1248
+ declineBg: "transparent",
1249
+ declineText: "#111827",
1250
+ declineBorder: "#d1d5db",
1251
+ shadow: "0 -4px 16px -2px rgba(15, 23, 42, 0.08), 0 -2px 6px -1px rgba(15, 23, 42, 0.04)",
1252
+ linkColor: "#1f2937"
1253
+ };
1254
+ var DARK_THEME = {
1255
+ background: "#0f172a",
1256
+ border: "#1e293b",
1257
+ text: "#f1f5f9",
1258
+ mutedText: "#cbd5e1",
1259
+ acceptBg: "#f1f5f9",
1260
+ acceptText: "#0f172a",
1261
+ declineBg: "transparent",
1262
+ declineText: "#f1f5f9",
1263
+ declineBorder: "#334155",
1264
+ shadow: "0 -4px 16px -2px rgba(0, 0, 0, 0.5), 0 -2px 6px -1px rgba(0, 0, 0, 0.3)",
1265
+ linkColor: "#e2e8f0"
1266
+ };
1267
+ function useResolvedTheme(theme) {
1268
+ const [prefersDark, setPrefersDark] = useState2(false);
1269
+ useEffect2(() => {
1270
+ if (theme !== "auto" || typeof window === "undefined" || !window.matchMedia) return;
1271
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
1272
+ setPrefersDark(mql.matches);
1273
+ const onChange = (e) => setPrefersDark(e.matches);
1274
+ mql.addEventListener("change", onChange);
1275
+ return () => mql.removeEventListener("change", onChange);
1276
+ }, [theme]);
1277
+ if (theme === "dark") return DARK_THEME;
1278
+ if (theme === "auto" && prefersDark) return DARK_THEME;
1279
+ return LIGHT_THEME;
1280
+ }
1281
+ var DEFAULT_MESSAGE = "We use cookies to understand ad performance and improve how our marketing works across visits. You can accept or decline this tracking.";
1282
+ function ConsentBanner({
1283
+ message,
1284
+ title,
1285
+ acceptLabel = "Accept",
1286
+ declineLabel = "Decline",
1287
+ policyHref,
1288
+ policyLabel = "Learn more",
1289
+ onAccept,
1290
+ onDecline,
1291
+ position = "bottom",
1292
+ theme = "light",
1293
+ className,
1294
+ style
1295
+ } = {}) {
1296
+ const { isPending, accept, decline } = useConsent();
1297
+ const tokens = useResolvedTheme(theme);
1298
+ const [hasMounted, setHasMounted] = useState2(false);
1299
+ useEffect2(() => {
1300
+ setHasMounted(true);
1301
+ }, []);
1302
+ if (!hasMounted || !isPending) return null;
1303
+ const wrapperStyle = {
1304
+ position: "fixed",
1305
+ left: 0,
1306
+ right: 0,
1307
+ [position]: 0,
1308
+ zIndex: 2147483640,
1309
+ background: tokens.background,
1310
+ color: tokens.text,
1311
+ borderTop: position === "bottom" ? `1px solid ${tokens.border}` : "none",
1312
+ borderBottom: position === "top" ? `1px solid ${tokens.border}` : "none",
1313
+ boxShadow: tokens.shadow,
1314
+ padding: "16px 20px",
1315
+ boxSizing: "border-box",
1316
+ animation: `${ANIMATION_NAME}-${position} 200ms ease-out`,
1317
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
1318
+ ...style
1319
+ };
1320
+ const innerStyle = {
1321
+ maxWidth: 1100,
1322
+ margin: "0 auto",
1323
+ display: "flex",
1324
+ flexWrap: "wrap",
1325
+ gap: 16,
1326
+ alignItems: "center",
1327
+ justifyContent: "space-between"
1328
+ };
1329
+ const messageStyle = {
1330
+ flex: "1 1 320px",
1331
+ margin: 0,
1332
+ fontSize: 14,
1333
+ lineHeight: 1.5,
1334
+ color: tokens.mutedText
1335
+ };
1336
+ const titleStyle = {
1337
+ margin: "0 0 4px 0",
1338
+ fontSize: 14,
1339
+ fontWeight: 600,
1340
+ color: tokens.text
1341
+ };
1342
+ const actionsStyle = {
1343
+ display: "flex",
1344
+ gap: 8,
1345
+ flexShrink: 0
1346
+ };
1347
+ const buttonBase = {
1348
+ appearance: "none",
1349
+ fontFamily: "inherit",
1350
+ fontSize: 14,
1351
+ fontWeight: 500,
1352
+ padding: "8px 16px",
1353
+ borderRadius: 6,
1354
+ cursor: "pointer",
1355
+ border: "1px solid transparent",
1356
+ transition: "opacity 120ms ease"
1357
+ };
1358
+ const declineStyle = {
1359
+ ...buttonBase,
1360
+ background: tokens.declineBg,
1361
+ color: tokens.declineText,
1362
+ borderColor: tokens.declineBorder
1363
+ };
1364
+ const acceptStyle = {
1365
+ ...buttonBase,
1366
+ background: tokens.acceptBg,
1367
+ color: tokens.acceptText
1368
+ };
1369
+ const linkStyle = {
1370
+ color: tokens.linkColor,
1371
+ textDecoration: "underline",
1372
+ textUnderlineOffset: 2
1373
+ };
1374
+ const handleAccept = () => {
1375
+ accept();
1376
+ onAccept?.();
1377
+ };
1378
+ const handleDecline = () => {
1379
+ decline();
1380
+ onDecline?.();
1381
+ };
1382
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1383
+ /* @__PURE__ */ jsx("style", { children: ANIMATION_KEYFRAMES }),
1384
+ /* @__PURE__ */ jsx(
1385
+ "div",
1386
+ {
1387
+ role: "dialog",
1388
+ "aria-live": "polite",
1389
+ "aria-label": "Cookie consent",
1390
+ className,
1391
+ style: wrapperStyle,
1392
+ children: /* @__PURE__ */ jsxs("div", { style: innerStyle, children: [
1393
+ /* @__PURE__ */ jsxs("div", { style: { flex: "1 1 320px" }, children: [
1394
+ title ? /* @__PURE__ */ jsx("p", { style: titleStyle, children: title }) : null,
1395
+ /* @__PURE__ */ jsxs("p", { style: messageStyle, children: [
1396
+ message ?? DEFAULT_MESSAGE,
1397
+ policyHref ? /* @__PURE__ */ jsxs(Fragment, { children: [
1398
+ " ",
1399
+ /* @__PURE__ */ jsx("a", { href: policyHref, style: linkStyle, children: policyLabel })
1400
+ ] }) : null
1401
+ ] })
1402
+ ] }),
1403
+ /* @__PURE__ */ jsxs("div", { style: actionsStyle, children: [
1404
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleDecline, style: declineStyle, children: declineLabel }),
1405
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleAccept, style: acceptStyle, children: acceptLabel })
1406
+ ] })
1407
+ ] })
1408
+ }
1409
+ )
1410
+ ] });
1411
+ }
1412
+ var ANIMATION_NAME = "aranova-consent-banner";
1413
+ var ANIMATION_KEYFRAMES = `
1414
+ @keyframes ${ANIMATION_NAME}-bottom {
1415
+ from { transform: translateY(100%); opacity: 0; }
1416
+ to { transform: translateY(0); opacity: 1; }
1417
+ }
1418
+ @keyframes ${ANIMATION_NAME}-top {
1419
+ from { transform: translateY(-100%); opacity: 0; }
1420
+ to { transform: translateY(0); opacity: 1; }
1243
1421
  }
1422
+ `;
1244
1423
 
1245
1424
  // src/GoogleAdsTracking.tsx
1246
1425
  import Script from "next/script";
1247
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1426
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1248
1427
  function GoogleAdsTracking(props) {
1249
1428
  const { gtagId, gtagIds } = props;
1250
1429
  const allIds = gtagIds ? Object.values(gtagIds) : gtagId ? [gtagId] : [];
1251
1430
  if (allIds.length === 0) return null;
1252
1431
  const loaderId = allIds[0];
1253
1432
  const initScript = allIds.length === 1 ? createGtagInitScript(allIds[0]) : createGtagInitScriptMulti(allIds);
1254
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
1433
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1255
1434
  /* @__PURE__ */ jsx2(Script, { id: "consent-default", strategy: "beforeInteractive", children: createConsentDefaultScript() }),
1256
1435
  /* @__PURE__ */ jsx2(
1257
1436
  Script,
@@ -1269,7 +1448,7 @@ function GoogleAdsTracking(props) {
1269
1448
  import { usePathname, useSearchParams } from "next/navigation";
1270
1449
 
1271
1450
  // package.json
1272
- var version = "0.9.0";
1451
+ var version = "0.10.0";
1273
1452
 
1274
1453
  // src/factory.tsx
1275
1454
  import {
@@ -1494,8 +1673,10 @@ export {
1494
1673
  formatMoney,
1495
1674
  fromMinor,
1496
1675
  getConsentState,
1676
+ resetConsent,
1497
1677
  setConsentState,
1498
1678
  toMinor,
1679
+ useConsent,
1499
1680
  useConsentState,
1500
1681
  useGclid,
1501
1682
  useTrackingParams