@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/dist/index.js CHANGED
@@ -44,8 +44,10 @@ __export(src_exports, {
44
44
  formatMoney: () => formatMoney,
45
45
  fromMinor: () => fromMinor,
46
46
  getConsentState: () => getConsentState,
47
+ resetConsent: () => resetConsent,
47
48
  setConsentState: () => setConsentState,
48
49
  toMinor: () => toMinor,
50
+ useConsent: () => useConsent,
49
51
  useConsentState: () => useConsentState,
50
52
  useGclid: () => useGclid,
51
53
  useTrackingParams: () => useTrackingParams
@@ -79,6 +81,12 @@ function setConsentState(state) {
79
81
  if (typeof window.gtag === "function")
80
82
  window.gtag("consent", "update", buildConsentPayload(state));
81
83
  }
84
+ function resetConsent() {
85
+ if (typeof window === "undefined")
86
+ return;
87
+ window.localStorage.removeItem(CONSENT_STATE_KEY);
88
+ window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
89
+ }
82
90
 
83
91
  // ../tracking-core/src/payloads.ts
84
92
  function createTrackingClientContext(surface, input = {}) {
@@ -1220,81 +1228,244 @@ function formatMoney(cents, currency, locale) {
1220
1228
  }
1221
1229
 
1222
1230
  // src/ConsentBanner.tsx
1223
- var import_react = require("react");
1224
- var import_jsx_runtime = require("react/jsx-runtime");
1225
- function ConsentBanner() {
1226
- const [consentState, setBannerConsentState] = (0, import_react.useState)("pending");
1227
- const [isReady, setIsReady] = (0, import_react.useState)(false);
1228
- (0, import_react.useEffect)(() => {
1229
- setBannerConsentState(getConsentState());
1230
- setIsReady(true);
1231
- }, []);
1232
- if (!isReady || consentState !== "pending")
1233
- return null;
1234
- const handleAccept = () => {
1235
- setConsentState("granted");
1236
- setBannerConsentState("granted");
1237
- };
1238
- const handleDecline = () => {
1239
- setConsentState("denied");
1240
- setBannerConsentState("denied");
1241
- };
1242
- return /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between", children: [
1243
- /* @__PURE__ */ (0, import_jsx_runtime.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." }),
1244
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
1245
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1246
- "button",
1247
- {
1248
- type: "button",
1249
- onClick: handleDecline,
1250
- className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100",
1251
- children: "Decline"
1252
- }
1253
- ),
1254
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1255
- "button",
1256
- {
1257
- type: "button",
1258
- onClick: handleAccept,
1259
- className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800",
1260
- children: "Accept"
1261
- }
1262
- )
1263
- ] })
1264
- ] }) });
1265
- }
1231
+ var import_react2 = require("react");
1266
1232
 
1267
1233
  // src/hooks.ts
1268
- var import_react2 = require("react");
1234
+ var import_react = require("react");
1269
1235
  function useGclid() {
1270
- const [gclid, setGclid] = (0, import_react2.useState)(null);
1271
- (0, import_react2.useEffect)(() => {
1236
+ const [gclid, setGclid] = (0, import_react.useState)(null);
1237
+ (0, import_react.useEffect)(() => {
1272
1238
  setGclid(getCookieValueFromDocument("gclid"));
1273
1239
  }, []);
1274
1240
  return gclid;
1275
1241
  }
1276
1242
  function useTrackingParams() {
1277
- const [trackingParams, setTrackingParams] = (0, import_react2.useState)(createEmptyTrackingParams());
1278
- (0, import_react2.useEffect)(() => {
1243
+ const [trackingParams, setTrackingParams] = (0, import_react.useState)(createEmptyTrackingParams());
1244
+ (0, import_react.useEffect)(() => {
1279
1245
  setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
1280
1246
  }, []);
1281
1247
  return trackingParams;
1282
1248
  }
1283
1249
  function useConsentState() {
1284
- const [consentState, setConsentStateValue] = (0, import_react2.useState)("pending");
1285
- (0, import_react2.useEffect)(() => {
1286
- setConsentStateValue(getConsentState());
1250
+ return useConsent().state;
1251
+ }
1252
+ function useConsent() {
1253
+ const [state, setState] = (0, import_react.useState)("pending");
1254
+ (0, import_react.useEffect)(() => {
1255
+ setState(getConsentState());
1287
1256
  const handleStorage = (event) => {
1288
- if (event.key === "consent_state")
1289
- setConsentStateValue(getConsentState());
1257
+ if (event.key === CONSENT_STATE_KEY) setState(getConsentState());
1290
1258
  };
1291
1259
  window.addEventListener("storage", handleStorage);
1292
- return () => {
1293
- window.removeEventListener("storage", handleStorage);
1294
- };
1260
+ return () => window.removeEventListener("storage", handleStorage);
1261
+ }, []);
1262
+ const accept = (0, import_react.useCallback)(() => {
1263
+ setConsentState("granted");
1264
+ setState("granted");
1295
1265
  }, []);
1296
- return consentState;
1266
+ const decline = (0, import_react.useCallback)(() => {
1267
+ setConsentState("denied");
1268
+ setState("denied");
1269
+ }, []);
1270
+ const reset = (0, import_react.useCallback)(() => {
1271
+ resetConsent();
1272
+ setState("pending");
1273
+ }, []);
1274
+ return {
1275
+ state,
1276
+ isPending: state === "pending",
1277
+ isGranted: state === "granted",
1278
+ isDenied: state === "denied",
1279
+ accept,
1280
+ decline,
1281
+ reset
1282
+ };
1283
+ }
1284
+
1285
+ // src/ConsentBanner.tsx
1286
+ var import_jsx_runtime = require("react/jsx-runtime");
1287
+ var LIGHT_THEME = {
1288
+ background: "#ffffff",
1289
+ border: "#e5e7eb",
1290
+ text: "#111827",
1291
+ mutedText: "#4b5563",
1292
+ acceptBg: "#111827",
1293
+ acceptText: "#ffffff",
1294
+ declineBg: "transparent",
1295
+ declineText: "#111827",
1296
+ declineBorder: "#d1d5db",
1297
+ shadow: "0 -4px 16px -2px rgba(15, 23, 42, 0.08), 0 -2px 6px -1px rgba(15, 23, 42, 0.04)",
1298
+ linkColor: "#1f2937"
1299
+ };
1300
+ var DARK_THEME = {
1301
+ background: "#0f172a",
1302
+ border: "#1e293b",
1303
+ text: "#f1f5f9",
1304
+ mutedText: "#cbd5e1",
1305
+ acceptBg: "#f1f5f9",
1306
+ acceptText: "#0f172a",
1307
+ declineBg: "transparent",
1308
+ declineText: "#f1f5f9",
1309
+ declineBorder: "#334155",
1310
+ shadow: "0 -4px 16px -2px rgba(0, 0, 0, 0.5), 0 -2px 6px -1px rgba(0, 0, 0, 0.3)",
1311
+ linkColor: "#e2e8f0"
1312
+ };
1313
+ function useResolvedTheme(theme) {
1314
+ const [prefersDark, setPrefersDark] = (0, import_react2.useState)(false);
1315
+ (0, import_react2.useEffect)(() => {
1316
+ if (theme !== "auto" || typeof window === "undefined" || !window.matchMedia) return;
1317
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
1318
+ setPrefersDark(mql.matches);
1319
+ const onChange = (e) => setPrefersDark(e.matches);
1320
+ mql.addEventListener("change", onChange);
1321
+ return () => mql.removeEventListener("change", onChange);
1322
+ }, [theme]);
1323
+ if (theme === "dark") return DARK_THEME;
1324
+ if (theme === "auto" && prefersDark) return DARK_THEME;
1325
+ return LIGHT_THEME;
1326
+ }
1327
+ 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.";
1328
+ function ConsentBanner({
1329
+ message,
1330
+ title,
1331
+ acceptLabel = "Accept",
1332
+ declineLabel = "Decline",
1333
+ policyHref,
1334
+ policyLabel = "Learn more",
1335
+ onAccept,
1336
+ onDecline,
1337
+ position = "bottom",
1338
+ theme = "light",
1339
+ className,
1340
+ style
1341
+ } = {}) {
1342
+ const { isPending, accept, decline } = useConsent();
1343
+ const tokens = useResolvedTheme(theme);
1344
+ const [hasMounted, setHasMounted] = (0, import_react2.useState)(false);
1345
+ (0, import_react2.useEffect)(() => {
1346
+ setHasMounted(true);
1347
+ }, []);
1348
+ if (!hasMounted || !isPending) return null;
1349
+ const wrapperStyle = {
1350
+ position: "fixed",
1351
+ left: 0,
1352
+ right: 0,
1353
+ [position]: 0,
1354
+ zIndex: 2147483640,
1355
+ background: tokens.background,
1356
+ color: tokens.text,
1357
+ borderTop: position === "bottom" ? `1px solid ${tokens.border}` : "none",
1358
+ borderBottom: position === "top" ? `1px solid ${tokens.border}` : "none",
1359
+ boxShadow: tokens.shadow,
1360
+ padding: "16px 20px",
1361
+ boxSizing: "border-box",
1362
+ animation: `${ANIMATION_NAME}-${position} 200ms ease-out`,
1363
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
1364
+ ...style
1365
+ };
1366
+ const innerStyle = {
1367
+ maxWidth: 1100,
1368
+ margin: "0 auto",
1369
+ display: "flex",
1370
+ flexWrap: "wrap",
1371
+ gap: 16,
1372
+ alignItems: "center",
1373
+ justifyContent: "space-between"
1374
+ };
1375
+ const messageStyle = {
1376
+ flex: "1 1 320px",
1377
+ margin: 0,
1378
+ fontSize: 14,
1379
+ lineHeight: 1.5,
1380
+ color: tokens.mutedText
1381
+ };
1382
+ const titleStyle = {
1383
+ margin: "0 0 4px 0",
1384
+ fontSize: 14,
1385
+ fontWeight: 600,
1386
+ color: tokens.text
1387
+ };
1388
+ const actionsStyle = {
1389
+ display: "flex",
1390
+ gap: 8,
1391
+ flexShrink: 0
1392
+ };
1393
+ const buttonBase = {
1394
+ appearance: "none",
1395
+ fontFamily: "inherit",
1396
+ fontSize: 14,
1397
+ fontWeight: 500,
1398
+ padding: "8px 16px",
1399
+ borderRadius: 6,
1400
+ cursor: "pointer",
1401
+ border: "1px solid transparent",
1402
+ transition: "opacity 120ms ease"
1403
+ };
1404
+ const declineStyle = {
1405
+ ...buttonBase,
1406
+ background: tokens.declineBg,
1407
+ color: tokens.declineText,
1408
+ borderColor: tokens.declineBorder
1409
+ };
1410
+ const acceptStyle = {
1411
+ ...buttonBase,
1412
+ background: tokens.acceptBg,
1413
+ color: tokens.acceptText
1414
+ };
1415
+ const linkStyle = {
1416
+ color: tokens.linkColor,
1417
+ textDecoration: "underline",
1418
+ textUnderlineOffset: 2
1419
+ };
1420
+ const handleAccept = () => {
1421
+ accept();
1422
+ onAccept?.();
1423
+ };
1424
+ const handleDecline = () => {
1425
+ decline();
1426
+ onDecline?.();
1427
+ };
1428
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: ANIMATION_KEYFRAMES }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1431
+ "div",
1432
+ {
1433
+ role: "dialog",
1434
+ "aria-live": "polite",
1435
+ "aria-label": "Cookie consent",
1436
+ className,
1437
+ style: wrapperStyle,
1438
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: innerStyle, children: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "1 1 320px" }, children: [
1440
+ title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: titleStyle, children: title }) : null,
1441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: messageStyle, children: [
1442
+ message ?? DEFAULT_MESSAGE,
1443
+ policyHref ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1444
+ " ",
1445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: policyHref, style: linkStyle, children: policyLabel })
1446
+ ] }) : null
1447
+ ] })
1448
+ ] }),
1449
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: actionsStyle, children: [
1450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: handleDecline, style: declineStyle, children: declineLabel }),
1451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: handleAccept, style: acceptStyle, children: acceptLabel })
1452
+ ] })
1453
+ ] })
1454
+ }
1455
+ )
1456
+ ] });
1457
+ }
1458
+ var ANIMATION_NAME = "aranova-consent-banner";
1459
+ var ANIMATION_KEYFRAMES = `
1460
+ @keyframes ${ANIMATION_NAME}-bottom {
1461
+ from { transform: translateY(100%); opacity: 0; }
1462
+ to { transform: translateY(0); opacity: 1; }
1463
+ }
1464
+ @keyframes ${ANIMATION_NAME}-top {
1465
+ from { transform: translateY(-100%); opacity: 0; }
1466
+ to { transform: translateY(0); opacity: 1; }
1297
1467
  }
1468
+ `;
1298
1469
 
1299
1470
  // src/GoogleAdsTracking.tsx
1300
1471
  var import_script = __toESM(require("next/script"));
@@ -1323,7 +1494,7 @@ function GoogleAdsTracking(props) {
1323
1494
  var import_navigation = require("next/navigation");
1324
1495
 
1325
1496
  // package.json
1326
- var version = "0.8.0";
1497
+ var version = "0.9.1";
1327
1498
 
1328
1499
  // src/factory.tsx
1329
1500
  var import_react3 = require("react");
@@ -1543,8 +1714,10 @@ function createTracking(options) {
1543
1714
  formatMoney,
1544
1715
  fromMinor,
1545
1716
  getConsentState,
1717
+ resetConsent,
1546
1718
  setConsentState,
1547
1719
  toMinor,
1720
+ useConsent,
1548
1721
  useConsentState,
1549
1722
  useGclid,
1550
1723
  useTrackingParams