@aranova/tracking-react 0.9.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
@@ -34,8 +34,10 @@ __export(src_exports, {
34
34
  formatMoney: () => formatMoney,
35
35
  fromMinor: () => fromMinor,
36
36
  getConsentState: () => getConsentState,
37
+ resetConsent: () => resetConsent,
37
38
  setConsentState: () => setConsentState,
38
39
  toMinor: () => toMinor,
40
+ useConsent: () => useConsent,
39
41
  useConsentState: () => useConsentState,
40
42
  useGclid: () => useGclid,
41
43
  useTrackingParams: () => useTrackingParams
@@ -43,6 +45,9 @@ __export(src_exports, {
43
45
  module.exports = __toCommonJS(src_exports);
44
46
 
45
47
  // src/ConsentBanner.tsx
48
+ var import_react2 = require("react");
49
+
50
+ // src/hooks.ts
46
51
  var import_react = require("react");
47
52
 
48
53
  // ../tracking-core/src/consent.ts
@@ -72,6 +77,12 @@ function setConsentState(state) {
72
77
  if (typeof window.gtag === "function")
73
78
  window.gtag("consent", "update", buildConsentPayload(state));
74
79
  }
80
+ function resetConsent() {
81
+ if (typeof window === "undefined")
82
+ return;
83
+ window.localStorage.removeItem(CONSENT_STATE_KEY);
84
+ window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
85
+ }
75
86
  function restoreStoredConsent() {
76
87
  const consentState = getConsentState();
77
88
  if (consentState === "granted" || consentState === "denied")
@@ -1412,59 +1423,251 @@ function formatMoney(cents, currency, locale) {
1412
1423
  );
1413
1424
  }
1414
1425
 
1426
+ // src/hooks.ts
1427
+ function useGclid() {
1428
+ const [gclid, setGclid] = (0, import_react.useState)(null);
1429
+ (0, import_react.useEffect)(() => {
1430
+ setGclid(getCookieValueFromDocument("gclid"));
1431
+ }, []);
1432
+ return gclid;
1433
+ }
1434
+ function useTrackingParams() {
1435
+ const [trackingParams, setTrackingParams] = (0, import_react.useState)(createEmptyTrackingParams());
1436
+ (0, import_react.useEffect)(() => {
1437
+ setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
1438
+ }, []);
1439
+ return trackingParams;
1440
+ }
1441
+ function useConsentState() {
1442
+ return useConsent().state;
1443
+ }
1444
+ function useConsent() {
1445
+ const [state, setState] = (0, import_react.useState)("pending");
1446
+ (0, import_react.useEffect)(() => {
1447
+ setState(getConsentState());
1448
+ const handleStorage = (event) => {
1449
+ if (event.key === CONSENT_STATE_KEY) setState(getConsentState());
1450
+ };
1451
+ window.addEventListener("storage", handleStorage);
1452
+ return () => window.removeEventListener("storage", handleStorage);
1453
+ }, []);
1454
+ const accept = (0, import_react.useCallback)(() => {
1455
+ setConsentState("granted");
1456
+ setState("granted");
1457
+ }, []);
1458
+ const decline = (0, import_react.useCallback)(() => {
1459
+ setConsentState("denied");
1460
+ setState("denied");
1461
+ }, []);
1462
+ const reset = (0, import_react.useCallback)(() => {
1463
+ resetConsent();
1464
+ setState("pending");
1465
+ }, []);
1466
+ return {
1467
+ state,
1468
+ isPending: state === "pending",
1469
+ isGranted: state === "granted",
1470
+ isDenied: state === "denied",
1471
+ accept,
1472
+ decline,
1473
+ reset
1474
+ };
1475
+ }
1476
+
1415
1477
  // src/ConsentBanner.tsx
1416
1478
  var import_jsx_runtime = require("react/jsx-runtime");
1417
- function ConsentBanner() {
1418
- const [consentState, setBannerConsentState] = (0, import_react.useState)("pending");
1419
- const [isReady, setIsReady] = (0, import_react.useState)(false);
1420
- (0, import_react.useEffect)(() => {
1421
- setBannerConsentState(getConsentState());
1422
- setIsReady(true);
1479
+ var LIGHT_THEME = {
1480
+ background: "#ffffff",
1481
+ border: "#e5e7eb",
1482
+ text: "#111827",
1483
+ mutedText: "#4b5563",
1484
+ acceptBg: "#111827",
1485
+ acceptText: "#ffffff",
1486
+ declineBg: "transparent",
1487
+ declineText: "#111827",
1488
+ declineBorder: "#d1d5db",
1489
+ shadow: "0 -4px 16px -2px rgba(15, 23, 42, 0.08), 0 -2px 6px -1px rgba(15, 23, 42, 0.04)",
1490
+ linkColor: "#1f2937"
1491
+ };
1492
+ var DARK_THEME = {
1493
+ background: "#0f172a",
1494
+ border: "#1e293b",
1495
+ text: "#f1f5f9",
1496
+ mutedText: "#cbd5e1",
1497
+ acceptBg: "#f1f5f9",
1498
+ acceptText: "#0f172a",
1499
+ declineBg: "transparent",
1500
+ declineText: "#f1f5f9",
1501
+ declineBorder: "#334155",
1502
+ shadow: "0 -4px 16px -2px rgba(0, 0, 0, 0.5), 0 -2px 6px -1px rgba(0, 0, 0, 0.3)",
1503
+ linkColor: "#e2e8f0"
1504
+ };
1505
+ function useResolvedTheme(theme) {
1506
+ const [prefersDark, setPrefersDark] = (0, import_react2.useState)(false);
1507
+ (0, import_react2.useEffect)(() => {
1508
+ if (theme !== "auto" || typeof window === "undefined" || !window.matchMedia) return;
1509
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
1510
+ setPrefersDark(mql.matches);
1511
+ const onChange = (e) => setPrefersDark(e.matches);
1512
+ mql.addEventListener("change", onChange);
1513
+ return () => mql.removeEventListener("change", onChange);
1514
+ }, [theme]);
1515
+ if (theme === "dark") return DARK_THEME;
1516
+ if (theme === "auto" && prefersDark) return DARK_THEME;
1517
+ return LIGHT_THEME;
1518
+ }
1519
+ 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.";
1520
+ function ConsentBanner({
1521
+ message,
1522
+ title,
1523
+ acceptLabel = "Accept",
1524
+ declineLabel = "Decline",
1525
+ policyHref,
1526
+ policyLabel = "Learn more",
1527
+ onAccept,
1528
+ onDecline,
1529
+ position = "bottom",
1530
+ theme = "light",
1531
+ className,
1532
+ style
1533
+ } = {}) {
1534
+ const { isPending, accept, decline } = useConsent();
1535
+ const tokens = useResolvedTheme(theme);
1536
+ const [hasMounted, setHasMounted] = (0, import_react2.useState)(false);
1537
+ (0, import_react2.useEffect)(() => {
1538
+ setHasMounted(true);
1423
1539
  }, []);
1424
- if (!isReady || consentState !== "pending")
1425
- return null;
1540
+ if (!hasMounted || !isPending) return null;
1541
+ const wrapperStyle = {
1542
+ position: "fixed",
1543
+ left: 0,
1544
+ right: 0,
1545
+ [position]: 0,
1546
+ zIndex: 2147483640,
1547
+ background: tokens.background,
1548
+ color: tokens.text,
1549
+ borderTop: position === "bottom" ? `1px solid ${tokens.border}` : "none",
1550
+ borderBottom: position === "top" ? `1px solid ${tokens.border}` : "none",
1551
+ boxShadow: tokens.shadow,
1552
+ padding: "16px 20px",
1553
+ boxSizing: "border-box",
1554
+ animation: `${ANIMATION_NAME}-${position} 200ms ease-out`,
1555
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
1556
+ ...style
1557
+ };
1558
+ const innerStyle = {
1559
+ maxWidth: 1100,
1560
+ margin: "0 auto",
1561
+ display: "flex",
1562
+ flexWrap: "wrap",
1563
+ gap: 16,
1564
+ alignItems: "center",
1565
+ justifyContent: "space-between"
1566
+ };
1567
+ const messageStyle = {
1568
+ flex: "1 1 320px",
1569
+ margin: 0,
1570
+ fontSize: 14,
1571
+ lineHeight: 1.5,
1572
+ color: tokens.mutedText
1573
+ };
1574
+ const titleStyle = {
1575
+ margin: "0 0 4px 0",
1576
+ fontSize: 14,
1577
+ fontWeight: 600,
1578
+ color: tokens.text
1579
+ };
1580
+ const actionsStyle = {
1581
+ display: "flex",
1582
+ gap: 8,
1583
+ flexShrink: 0
1584
+ };
1585
+ const buttonBase = {
1586
+ appearance: "none",
1587
+ fontFamily: "inherit",
1588
+ fontSize: 14,
1589
+ fontWeight: 500,
1590
+ padding: "8px 16px",
1591
+ borderRadius: 6,
1592
+ cursor: "pointer",
1593
+ border: "1px solid transparent",
1594
+ transition: "opacity 120ms ease"
1595
+ };
1596
+ const declineStyle = {
1597
+ ...buttonBase,
1598
+ background: tokens.declineBg,
1599
+ color: tokens.declineText,
1600
+ borderColor: tokens.declineBorder
1601
+ };
1602
+ const acceptStyle = {
1603
+ ...buttonBase,
1604
+ background: tokens.acceptBg,
1605
+ color: tokens.acceptText
1606
+ };
1607
+ const linkStyle = {
1608
+ color: tokens.linkColor,
1609
+ textDecoration: "underline",
1610
+ textUnderlineOffset: 2
1611
+ };
1426
1612
  const handleAccept = () => {
1427
- setConsentState("granted");
1428
- setBannerConsentState("granted");
1613
+ accept();
1614
+ onAccept?.();
1429
1615
  };
1430
1616
  const handleDecline = () => {
1431
- setConsentState("denied");
1432
- setBannerConsentState("denied");
1617
+ decline();
1618
+ onDecline?.();
1433
1619
  };
1434
- 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: [
1435
- /* @__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." }),
1436
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
1437
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1438
- "button",
1439
- {
1440
- type: "button",
1441
- onClick: handleDecline,
1442
- className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100",
1443
- children: "Decline"
1444
- }
1445
- ),
1446
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1447
- "button",
1448
- {
1449
- type: "button",
1450
- onClick: handleAccept,
1451
- className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800",
1452
- children: "Accept"
1453
- }
1454
- )
1455
- ] })
1456
- ] }) });
1620
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: ANIMATION_KEYFRAMES }),
1622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1623
+ "div",
1624
+ {
1625
+ role: "dialog",
1626
+ "aria-live": "polite",
1627
+ "aria-label": "Cookie consent",
1628
+ className,
1629
+ style: wrapperStyle,
1630
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: innerStyle, children: [
1631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "1 1 320px" }, children: [
1632
+ title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: titleStyle, children: title }) : null,
1633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: messageStyle, children: [
1634
+ message ?? DEFAULT_MESSAGE,
1635
+ policyHref ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1636
+ " ",
1637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: policyHref, style: linkStyle, children: policyLabel })
1638
+ ] }) : null
1639
+ ] })
1640
+ ] }),
1641
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: actionsStyle, children: [
1642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: handleDecline, style: declineStyle, children: declineLabel }),
1643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: handleAccept, style: acceptStyle, children: acceptLabel })
1644
+ ] })
1645
+ ] })
1646
+ }
1647
+ )
1648
+ ] });
1649
+ }
1650
+ var ANIMATION_NAME = "aranova-consent-banner";
1651
+ var ANIMATION_KEYFRAMES = `
1652
+ @keyframes ${ANIMATION_NAME}-bottom {
1653
+ from { transform: translateY(100%); opacity: 0; }
1654
+ to { transform: translateY(0); opacity: 1; }
1655
+ }
1656
+ @keyframes ${ANIMATION_NAME}-top {
1657
+ from { transform: translateY(-100%); opacity: 0; }
1658
+ to { transform: translateY(0); opacity: 1; }
1457
1659
  }
1660
+ `;
1458
1661
 
1459
1662
  // src/GoogleAdsTracking.tsx
1460
- var import_react2 = require("react");
1663
+ var import_react3 = require("react");
1461
1664
  function GoogleAdsTracking(props) {
1462
1665
  const { gtagId, gtagIds } = props;
1463
- const gtagIdsKey = (0, import_react2.useMemo)(
1666
+ const gtagIdsKey = (0, import_react3.useMemo)(
1464
1667
  () => gtagIds ? JSON.stringify(gtagIds) : "",
1465
1668
  [gtagIds]
1466
1669
  );
1467
- (0, import_react2.useEffect)(() => {
1670
+ (0, import_react3.useEffect)(() => {
1468
1671
  if (gtagIds && Object.keys(gtagIds).length > 0) {
1469
1672
  bootstrapMultipleGtags(gtagIds);
1470
1673
  } else if (gtagId) {
@@ -1474,43 +1677,11 @@ function GoogleAdsTracking(props) {
1474
1677
  return null;
1475
1678
  }
1476
1679
 
1477
- // src/hooks.ts
1478
- var import_react3 = require("react");
1479
- function useGclid() {
1480
- const [gclid, setGclid] = (0, import_react3.useState)(null);
1481
- (0, import_react3.useEffect)(() => {
1482
- setGclid(getCookieValueFromDocument("gclid"));
1483
- }, []);
1484
- return gclid;
1485
- }
1486
- function useTrackingParams() {
1487
- const [trackingParams, setTrackingParams] = (0, import_react3.useState)(createEmptyTrackingParams());
1488
- (0, import_react3.useEffect)(() => {
1489
- setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
1490
- }, []);
1491
- return trackingParams;
1492
- }
1493
- function useConsentState() {
1494
- const [consentState, setConsentStateValue] = (0, import_react3.useState)("pending");
1495
- (0, import_react3.useEffect)(() => {
1496
- setConsentStateValue(getConsentState());
1497
- const handleStorage = (event) => {
1498
- if (event.key === "consent_state")
1499
- setConsentStateValue(getConsentState());
1500
- };
1501
- window.addEventListener("storage", handleStorage);
1502
- return () => {
1503
- window.removeEventListener("storage", handleStorage);
1504
- };
1505
- }, []);
1506
- return consentState;
1507
- }
1508
-
1509
1680
  // src/factory.tsx
1510
1681
  var import_react4 = require("react");
1511
1682
 
1512
1683
  // package.json
1513
- var version = "0.9.0";
1684
+ var version = "0.9.1";
1514
1685
 
1515
1686
  // src/factory.tsx
1516
1687
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -1634,8 +1805,10 @@ function createTracking(options) {
1634
1805
  formatMoney,
1635
1806
  fromMinor,
1636
1807
  getConsentState,
1808
+ resetConsent,
1637
1809
  setConsentState,
1638
1810
  toMinor,
1811
+ useConsent,
1639
1812
  useConsentState,
1640
1813
  useGclid,
1641
1814
  useTrackingParams