@aranova/tracking-react 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.mjs CHANGED
@@ -1,5 +1,8 @@
1
1
  // src/ConsentBanner.tsx
2
- import { useEffect, useState } from "react";
2
+ import { useEffect as useEffect2, useState as useState2 } from "react";
3
+
4
+ // src/hooks.ts
5
+ import { useCallback, useEffect, useState } from "react";
3
6
 
4
7
  // ../tracking-core/src/consent.ts
5
8
  var CONSENT_STATE_KEY = "consent_state";
@@ -28,6 +31,12 @@ function setConsentState(state) {
28
31
  if (typeof window.gtag === "function")
29
32
  window.gtag("consent", "update", buildConsentPayload(state));
30
33
  }
34
+ function resetConsent() {
35
+ if (typeof window === "undefined")
36
+ return;
37
+ window.localStorage.removeItem(CONSENT_STATE_KEY);
38
+ window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
39
+ }
31
40
  function restoreStoredConsent() {
32
41
  const consentState = getConsentState();
33
42
  if (consentState === "granted" || consentState === "denied")
@@ -1368,59 +1377,251 @@ function formatMoney(cents, currency, locale) {
1368
1377
  );
1369
1378
  }
1370
1379
 
1371
- // src/ConsentBanner.tsx
1372
- import { jsx, jsxs } from "react/jsx-runtime";
1373
- function ConsentBanner() {
1374
- const [consentState, setBannerConsentState] = useState("pending");
1375
- const [isReady, setIsReady] = useState(false);
1380
+ // src/hooks.ts
1381
+ function useGclid() {
1382
+ const [gclid, setGclid] = useState(null);
1376
1383
  useEffect(() => {
1377
- setBannerConsentState(getConsentState());
1378
- setIsReady(true);
1384
+ setGclid(getCookieValueFromDocument("gclid"));
1379
1385
  }, []);
1380
- if (!isReady || consentState !== "pending")
1381
- return null;
1382
- const handleAccept = () => {
1386
+ return gclid;
1387
+ }
1388
+ function useTrackingParams() {
1389
+ const [trackingParams, setTrackingParams] = useState(createEmptyTrackingParams());
1390
+ useEffect(() => {
1391
+ setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
1392
+ }, []);
1393
+ return trackingParams;
1394
+ }
1395
+ function useConsentState() {
1396
+ return useConsent().state;
1397
+ }
1398
+ function useConsent() {
1399
+ const [state, setState] = useState("pending");
1400
+ useEffect(() => {
1401
+ setState(getConsentState());
1402
+ const handleStorage = (event) => {
1403
+ if (event.key === CONSENT_STATE_KEY) setState(getConsentState());
1404
+ };
1405
+ window.addEventListener("storage", handleStorage);
1406
+ return () => window.removeEventListener("storage", handleStorage);
1407
+ }, []);
1408
+ const accept = useCallback(() => {
1383
1409
  setConsentState("granted");
1384
- setBannerConsentState("granted");
1410
+ setState("granted");
1411
+ }, []);
1412
+ const decline = useCallback(() => {
1413
+ setConsentState("denied");
1414
+ setState("denied");
1415
+ }, []);
1416
+ const reset = useCallback(() => {
1417
+ resetConsent();
1418
+ setState("pending");
1419
+ }, []);
1420
+ return {
1421
+ state,
1422
+ isPending: state === "pending",
1423
+ isGranted: state === "granted",
1424
+ isDenied: state === "denied",
1425
+ accept,
1426
+ decline,
1427
+ reset
1428
+ };
1429
+ }
1430
+
1431
+ // src/ConsentBanner.tsx
1432
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1433
+ var LIGHT_THEME = {
1434
+ background: "#ffffff",
1435
+ border: "#e5e7eb",
1436
+ text: "#111827",
1437
+ mutedText: "#4b5563",
1438
+ acceptBg: "#111827",
1439
+ acceptText: "#ffffff",
1440
+ declineBg: "transparent",
1441
+ declineText: "#111827",
1442
+ declineBorder: "#d1d5db",
1443
+ shadow: "0 -4px 16px -2px rgba(15, 23, 42, 0.08), 0 -2px 6px -1px rgba(15, 23, 42, 0.04)",
1444
+ linkColor: "#1f2937"
1445
+ };
1446
+ var DARK_THEME = {
1447
+ background: "#0f172a",
1448
+ border: "#1e293b",
1449
+ text: "#f1f5f9",
1450
+ mutedText: "#cbd5e1",
1451
+ acceptBg: "#f1f5f9",
1452
+ acceptText: "#0f172a",
1453
+ declineBg: "transparent",
1454
+ declineText: "#f1f5f9",
1455
+ declineBorder: "#334155",
1456
+ shadow: "0 -4px 16px -2px rgba(0, 0, 0, 0.5), 0 -2px 6px -1px rgba(0, 0, 0, 0.3)",
1457
+ linkColor: "#e2e8f0"
1458
+ };
1459
+ function useResolvedTheme(theme) {
1460
+ const [prefersDark, setPrefersDark] = useState2(false);
1461
+ useEffect2(() => {
1462
+ if (theme !== "auto" || typeof window === "undefined" || !window.matchMedia) return;
1463
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
1464
+ setPrefersDark(mql.matches);
1465
+ const onChange = (e) => setPrefersDark(e.matches);
1466
+ mql.addEventListener("change", onChange);
1467
+ return () => mql.removeEventListener("change", onChange);
1468
+ }, [theme]);
1469
+ if (theme === "dark") return DARK_THEME;
1470
+ if (theme === "auto" && prefersDark) return DARK_THEME;
1471
+ return LIGHT_THEME;
1472
+ }
1473
+ 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.";
1474
+ function ConsentBanner({
1475
+ message,
1476
+ title,
1477
+ acceptLabel = "Accept",
1478
+ declineLabel = "Decline",
1479
+ policyHref,
1480
+ policyLabel = "Learn more",
1481
+ onAccept,
1482
+ onDecline,
1483
+ position = "bottom",
1484
+ theme = "light",
1485
+ className,
1486
+ style
1487
+ } = {}) {
1488
+ const { isPending, accept, decline } = useConsent();
1489
+ const tokens = useResolvedTheme(theme);
1490
+ const [hasMounted, setHasMounted] = useState2(false);
1491
+ useEffect2(() => {
1492
+ setHasMounted(true);
1493
+ }, []);
1494
+ if (!hasMounted || !isPending) return null;
1495
+ const wrapperStyle = {
1496
+ position: "fixed",
1497
+ left: 0,
1498
+ right: 0,
1499
+ [position]: 0,
1500
+ zIndex: 2147483640,
1501
+ background: tokens.background,
1502
+ color: tokens.text,
1503
+ borderTop: position === "bottom" ? `1px solid ${tokens.border}` : "none",
1504
+ borderBottom: position === "top" ? `1px solid ${tokens.border}` : "none",
1505
+ boxShadow: tokens.shadow,
1506
+ padding: "16px 20px",
1507
+ boxSizing: "border-box",
1508
+ animation: `${ANIMATION_NAME}-${position} 200ms ease-out`,
1509
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
1510
+ ...style
1511
+ };
1512
+ const innerStyle = {
1513
+ maxWidth: 1100,
1514
+ margin: "0 auto",
1515
+ display: "flex",
1516
+ flexWrap: "wrap",
1517
+ gap: 16,
1518
+ alignItems: "center",
1519
+ justifyContent: "space-between"
1520
+ };
1521
+ const messageStyle = {
1522
+ flex: "1 1 320px",
1523
+ margin: 0,
1524
+ fontSize: 14,
1525
+ lineHeight: 1.5,
1526
+ color: tokens.mutedText
1527
+ };
1528
+ const titleStyle = {
1529
+ margin: "0 0 4px 0",
1530
+ fontSize: 14,
1531
+ fontWeight: 600,
1532
+ color: tokens.text
1533
+ };
1534
+ const actionsStyle = {
1535
+ display: "flex",
1536
+ gap: 8,
1537
+ flexShrink: 0
1538
+ };
1539
+ const buttonBase = {
1540
+ appearance: "none",
1541
+ fontFamily: "inherit",
1542
+ fontSize: 14,
1543
+ fontWeight: 500,
1544
+ padding: "8px 16px",
1545
+ borderRadius: 6,
1546
+ cursor: "pointer",
1547
+ border: "1px solid transparent",
1548
+ transition: "opacity 120ms ease"
1549
+ };
1550
+ const declineStyle = {
1551
+ ...buttonBase,
1552
+ background: tokens.declineBg,
1553
+ color: tokens.declineText,
1554
+ borderColor: tokens.declineBorder
1555
+ };
1556
+ const acceptStyle = {
1557
+ ...buttonBase,
1558
+ background: tokens.acceptBg,
1559
+ color: tokens.acceptText
1560
+ };
1561
+ const linkStyle = {
1562
+ color: tokens.linkColor,
1563
+ textDecoration: "underline",
1564
+ textUnderlineOffset: 2
1565
+ };
1566
+ const handleAccept = () => {
1567
+ accept();
1568
+ onAccept?.();
1385
1569
  };
1386
1570
  const handleDecline = () => {
1387
- setConsentState("denied");
1388
- setBannerConsentState("denied");
1571
+ decline();
1572
+ onDecline?.();
1389
1573
  };
1390
- 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: [
1391
- /* @__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." }),
1392
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
1393
- /* @__PURE__ */ jsx(
1394
- "button",
1395
- {
1396
- type: "button",
1397
- onClick: handleDecline,
1398
- className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100",
1399
- children: "Decline"
1400
- }
1401
- ),
1402
- /* @__PURE__ */ jsx(
1403
- "button",
1404
- {
1405
- type: "button",
1406
- onClick: handleAccept,
1407
- className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800",
1408
- children: "Accept"
1409
- }
1410
- )
1411
- ] })
1412
- ] }) });
1574
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1575
+ /* @__PURE__ */ jsx("style", { children: ANIMATION_KEYFRAMES }),
1576
+ /* @__PURE__ */ jsx(
1577
+ "div",
1578
+ {
1579
+ role: "dialog",
1580
+ "aria-live": "polite",
1581
+ "aria-label": "Cookie consent",
1582
+ className,
1583
+ style: wrapperStyle,
1584
+ children: /* @__PURE__ */ jsxs("div", { style: innerStyle, children: [
1585
+ /* @__PURE__ */ jsxs("div", { style: { flex: "1 1 320px" }, children: [
1586
+ title ? /* @__PURE__ */ jsx("p", { style: titleStyle, children: title }) : null,
1587
+ /* @__PURE__ */ jsxs("p", { style: messageStyle, children: [
1588
+ message ?? DEFAULT_MESSAGE,
1589
+ policyHref ? /* @__PURE__ */ jsxs(Fragment, { children: [
1590
+ " ",
1591
+ /* @__PURE__ */ jsx("a", { href: policyHref, style: linkStyle, children: policyLabel })
1592
+ ] }) : null
1593
+ ] })
1594
+ ] }),
1595
+ /* @__PURE__ */ jsxs("div", { style: actionsStyle, children: [
1596
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleDecline, style: declineStyle, children: declineLabel }),
1597
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: handleAccept, style: acceptStyle, children: acceptLabel })
1598
+ ] })
1599
+ ] })
1600
+ }
1601
+ )
1602
+ ] });
1603
+ }
1604
+ var ANIMATION_NAME = "aranova-consent-banner";
1605
+ var ANIMATION_KEYFRAMES = `
1606
+ @keyframes ${ANIMATION_NAME}-bottom {
1607
+ from { transform: translateY(100%); opacity: 0; }
1608
+ to { transform: translateY(0); opacity: 1; }
1413
1609
  }
1610
+ @keyframes ${ANIMATION_NAME}-top {
1611
+ from { transform: translateY(-100%); opacity: 0; }
1612
+ to { transform: translateY(0); opacity: 1; }
1613
+ }
1614
+ `;
1414
1615
 
1415
1616
  // src/GoogleAdsTracking.tsx
1416
- import { useEffect as useEffect2, useMemo } from "react";
1617
+ import { useEffect as useEffect3, useMemo } from "react";
1417
1618
  function GoogleAdsTracking(props) {
1418
1619
  const { gtagId, gtagIds } = props;
1419
1620
  const gtagIdsKey = useMemo(
1420
1621
  () => gtagIds ? JSON.stringify(gtagIds) : "",
1421
1622
  [gtagIds]
1422
1623
  );
1423
- useEffect2(() => {
1624
+ useEffect3(() => {
1424
1625
  if (gtagIds && Object.keys(gtagIds).length > 0) {
1425
1626
  bootstrapMultipleGtags(gtagIds);
1426
1627
  } else if (gtagId) {
@@ -1430,38 +1631,6 @@ function GoogleAdsTracking(props) {
1430
1631
  return null;
1431
1632
  }
1432
1633
 
1433
- // src/hooks.ts
1434
- import { useEffect as useEffect3, useState as useState2 } from "react";
1435
- function useGclid() {
1436
- const [gclid, setGclid] = useState2(null);
1437
- useEffect3(() => {
1438
- setGclid(getCookieValueFromDocument("gclid"));
1439
- }, []);
1440
- return gclid;
1441
- }
1442
- function useTrackingParams() {
1443
- const [trackingParams, setTrackingParams] = useState2(createEmptyTrackingParams());
1444
- useEffect3(() => {
1445
- setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
1446
- }, []);
1447
- return trackingParams;
1448
- }
1449
- function useConsentState() {
1450
- const [consentState, setConsentStateValue] = useState2("pending");
1451
- useEffect3(() => {
1452
- setConsentStateValue(getConsentState());
1453
- const handleStorage = (event) => {
1454
- if (event.key === "consent_state")
1455
- setConsentStateValue(getConsentState());
1456
- };
1457
- window.addEventListener("storage", handleStorage);
1458
- return () => {
1459
- window.removeEventListener("storage", handleStorage);
1460
- };
1461
- }, []);
1462
- return consentState;
1463
- }
1464
-
1465
1634
  // src/factory.tsx
1466
1635
  import {
1467
1636
  createContext,
@@ -1471,7 +1640,7 @@ import {
1471
1640
  } from "react";
1472
1641
 
1473
1642
  // package.json
1474
- var version = "0.8.0";
1643
+ var version = "0.9.1";
1475
1644
 
1476
1645
  // src/factory.tsx
1477
1646
  import { jsx as jsx2 } from "react/jsx-runtime";
@@ -1594,8 +1763,10 @@ export {
1594
1763
  formatMoney,
1595
1764
  fromMinor,
1596
1765
  getConsentState,
1766
+ resetConsent,
1597
1767
  setConsentState,
1598
1768
  toMinor,
1769
+ useConsent,
1599
1770
  useConsentState,
1600
1771
  useGclid,
1601
1772
  useTrackingParams