@whatworks/analytics 1.0.0 → 1.0.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.
@@ -6,7 +6,7 @@ import { CookieBannerPortal } from './CookieBannerPortal.js';
6
6
  import { useCookieBanner } from './CookieBannerProvider.js';
7
7
  export default function CookieBanner({ acceptText = 'Accept', description = /*#__PURE__*/ _jsxs(_Fragment, {
8
8
  children: [
9
- 'We use cookies to improve your experience. By clicking "Accept", you agree to the use of cookies. To learn more, please view our',
9
+ "We use essential cookies to ensure the site functions properly, and optional cookies to improve your experience. By clicking Accept”, you consent to the use of non-essential cookies. To learn more, please view our",
10
10
  ' ',
11
11
  /*#__PURE__*/ _jsx(Link, {
12
12
  className: "underline",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/CookieBanner.tsx"],"sourcesContent":["'use client'\nimport type { ReactNode } from 'react'\n\nimport './styles.css'\n\nimport Link from 'next/link'\n\nimport { CookieBannerPortal } from './CookieBannerPortal.js'\nimport { useCookieBanner } from './CookieBannerProvider.js'\n\nexport default function CookieBanner({\n acceptText = 'Accept',\n description = (\n <>\n We use cookies to improve your experience. By clicking \"Accept\", you agree to the use of\n cookies. To learn more, please view our{' '}\n <Link className=\"underline\" href=\"/privacy-policy\">\n Privacy Policy\n </Link>\n .\n </>\n ),\n rejectText = 'Reject',\n title = 'Cookies',\n}: {\n acceptText?: string\n description?: ReactNode\n rejectText?: string\n title?: string\n}) {\n const { accept, reject, shouldShowBanner } = useCookieBanner()\n\n if (!shouldShowBanner) {\n return null\n }\n\n return (\n <CookieBannerPortal>\n <div className=\"ww\">\n <div className=\"fixed bottom-4 inset-x-4 z-50 border border-gray-200 rounded-lg bg-white py-8 shadow\">\n <div className=\"container mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"flex flex-col justify-between gap-4 md:flex-row md:items-center\">\n <div className=\"space-y-2\">\n <h3 className=\"text-base font-medium\">{title}</h3>\n <p className=\"text-sm text-gray-600\">{description}</p>\n </div>\n <div className=\"flex shrink-0 items-center gap-4\">\n <button\n className=\"py-3 px-6 bg-black text-white rounded-md hover:bg-stone-800\"\n onClick={() => {\n reject()\n }}\n type=\"button\"\n >\n {rejectText}\n </button>\n <button\n className=\"py-3 px-6 bg-black text-white rounded-md hover:bg-stone-800\"\n onClick={() => {\n accept()\n }}\n type=\"button\"\n >\n {acceptText}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </CookieBannerPortal>\n )\n}\n"],"names":["Link","CookieBannerPortal","useCookieBanner","CookieBanner","acceptText","description","className","href","rejectText","title","accept","reject","shouldShowBanner","div","h3","p","button","onClick","type"],"mappings":"AAAA;;AAGA,OAAO,eAAc;AAErB,OAAOA,UAAU,YAAW;AAE5B,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,eAAe,QAAQ,4BAA2B;AAE3D,eAAe,SAASC,aAAa,EACnCC,aAAa,QAAQ,EACrBC,4BACE;;QAAE;QAEwC;sBACxC,KAACL;YAAKM,WAAU;YAAYC,MAAK;sBAAkB;;QAE5C;;EAGV,EACDC,aAAa,QAAQ,EACrBC,QAAQ,SAAS,EAMlB;IACC,MAAM,EAAEC,MAAM,EAAEC,MAAM,EAAEC,gBAAgB,EAAE,GAAGV;IAE7C,IAAI,CAACU,kBAAkB;QACrB,OAAO;IACT;IAEA,qBACE,KAACX;kBACC,cAAA,KAACY;YAAIP,WAAU;sBACb,cAAA,KAACO;gBAAIP,WAAU;0BACb,cAAA,KAACO;oBAAIP,WAAU;8BACb,cAAA,MAACO;wBAAIP,WAAU;;0CACb,MAACO;gCAAIP,WAAU;;kDACb,KAACQ;wCAAGR,WAAU;kDAAyBG;;kDACvC,KAACM;wCAAET,WAAU;kDAAyBD;;;;0CAExC,MAACQ;gCAAIP,WAAU;;kDACb,KAACU;wCACCV,WAAU;wCACVW,SAAS;4CACPN;wCACF;wCACAO,MAAK;kDAEJV;;kDAEH,KAACQ;wCACCV,WAAU;wCACVW,SAAS;4CACPP;wCACF;wCACAQ,MAAK;kDAEJd;;;;;;;;;;AASnB"}
1
+ {"version":3,"sources":["../src/CookieBanner.tsx"],"sourcesContent":["'use client'\nimport type { ReactNode } from 'react'\n\nimport './styles.css'\n\nimport Link from 'next/link'\n\nimport { CookieBannerPortal } from './CookieBannerPortal.js'\nimport { useCookieBanner } from './CookieBannerProvider.js'\n\nexport default function CookieBanner({\n acceptText = 'Accept',\n description = (\n <>\n We use essential cookies to ensure the site functions properly, and optional cookies to\n improve your experience. By clicking Accept”, you consent to the use of non-essential\n cookies. To learn more, please view our{' '}\n <Link className=\"underline\" href=\"/privacy-policy\">\n Privacy Policy\n </Link>\n .\n </>\n ),\n rejectText = 'Reject',\n title = 'Cookies',\n}: {\n acceptText?: string\n description?: ReactNode\n rejectText?: string\n title?: string\n}) {\n const { accept, reject, shouldShowBanner } = useCookieBanner()\n\n if (!shouldShowBanner) {\n return null\n }\n\n return (\n <CookieBannerPortal>\n <div className=\"ww\">\n <div className=\"fixed bottom-4 inset-x-4 z-50 border border-gray-200 rounded-lg bg-white py-8 shadow\">\n <div className=\"container mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"flex flex-col justify-between gap-4 md:flex-row md:items-center\">\n <div className=\"space-y-2\">\n <h3 className=\"text-base font-medium\">{title}</h3>\n <p className=\"text-sm text-gray-600\">{description}</p>\n </div>\n <div className=\"flex shrink-0 items-center gap-4\">\n <button\n className=\"py-3 px-6 bg-black text-white rounded-md hover:bg-stone-800\"\n onClick={() => {\n reject()\n }}\n type=\"button\"\n >\n {rejectText}\n </button>\n <button\n className=\"py-3 px-6 bg-black text-white rounded-md hover:bg-stone-800\"\n onClick={() => {\n accept()\n }}\n type=\"button\"\n >\n {acceptText}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </CookieBannerPortal>\n )\n}\n"],"names":["Link","CookieBannerPortal","useCookieBanner","CookieBanner","acceptText","description","className","href","rejectText","title","accept","reject","shouldShowBanner","div","h3","p","button","onClick","type"],"mappings":"AAAA;;AAGA,OAAO,eAAc;AAErB,OAAOA,UAAU,YAAW;AAE5B,SAASC,kBAAkB,QAAQ,0BAAyB;AAC5D,SAASC,eAAe,QAAQ,4BAA2B;AAE3D,eAAe,SAASC,aAAa,EACnCC,aAAa,QAAQ,EACrBC,4BACE;;QAAE;QAGwC;sBACxC,KAACL;YAAKM,WAAU;YAAYC,MAAK;sBAAkB;;QAE5C;;EAGV,EACDC,aAAa,QAAQ,EACrBC,QAAQ,SAAS,EAMlB;IACC,MAAM,EAAEC,MAAM,EAAEC,MAAM,EAAEC,gBAAgB,EAAE,GAAGV;IAE7C,IAAI,CAACU,kBAAkB;QACrB,OAAO;IACT;IAEA,qBACE,KAACX;kBACC,cAAA,KAACY;YAAIP,WAAU;sBACb,cAAA,KAACO;gBAAIP,WAAU;0BACb,cAAA,KAACO;oBAAIP,WAAU;8BACb,cAAA,MAACO;wBAAIP,WAAU;;0CACb,MAACO;gCAAIP,WAAU;;kDACb,KAACQ;wCAAGR,WAAU;kDAAyBG;;kDACvC,KAACM;wCAAET,WAAU;kDAAyBD;;;;0CAExC,MAACQ;gCAAIP,WAAU;;kDACb,KAACU;wCACCV,WAAU;wCACVW,SAAS;4CACPN;wCACF;wCACAO,MAAK;kDAEJV;;kDAEH,KAACQ;wCACCV,WAAU;wCACVW,SAAS;4CACPP;wCACF;wCACAQ,MAAK;kDAEJd;;;;;;;;;;AASnB"}
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- export type ConsentStrategy = 'load-scripts-revoke-consent-immediately' | 'load-scripts-then-revoke-consent-after-geolocation-check' | 'require-consent-before-loading-scripts';
2
+ export type ConsentStrategy = 'load-scripts-always-grant-consent' | 'load-scripts-revoke-consent-immediately' | 'load-scripts-then-revoke-consent-after-geolocation-check' | 'require-consent-before-loading-scripts';
3
3
  type ConsentStatus = 'denied' | 'granted';
4
4
  interface CookieBannerContextType {
5
5
  accept: () => void;
@@ -26,6 +26,11 @@ interface CookieBannerProviderProps {
26
26
  * `require-consent-before-loading-scripts`
27
27
  * - Do not render scripts until consent is granted when required.
28
28
  * - Banner shown only if geolocation requires consent.
29
+ *
30
+ * `load-scripts-always-grant-consent`
31
+ * - Render scripts immediately.
32
+ * - Consent is always granted, regardless of geolocation.
33
+ * - Banner is never shown.
29
34
  */
30
35
  consentStrategy: ConsentStrategy;
31
36
  }
@@ -10,6 +10,60 @@ export function useCookieBanner() {
10
10
  return context;
11
11
  }
12
12
  const CONSENT_STORAGE_KEY = 'cookiesAllowed';
13
+ const STRATEGY_CONFIG = {
14
+ 'load-scripts-always-grant-consent': {
15
+ initialDecision: 'granted',
16
+ initialRequiresConsent: false,
17
+ resolveWithDecision: ()=>({
18
+ consentStatus: 'granted',
19
+ shouldLoadScripts: true
20
+ }),
21
+ resolveWithoutDecision: ()=>({
22
+ consentStatus: 'granted',
23
+ shouldLoadScripts: true
24
+ }),
25
+ shouldSkipGeolocation: true
26
+ },
27
+ 'load-scripts-revoke-consent-immediately': {
28
+ initialDecision: null,
29
+ initialRequiresConsent: null,
30
+ resolveWithDecision: (decision)=>({
31
+ consentStatus: decision,
32
+ shouldLoadScripts: true
33
+ }),
34
+ resolveWithoutDecision: (requiresConsent)=>({
35
+ consentStatus: requiresConsent === false ? 'granted' : 'denied',
36
+ shouldLoadScripts: true
37
+ }),
38
+ shouldSkipGeolocation: false
39
+ },
40
+ 'load-scripts-then-revoke-consent-after-geolocation-check': {
41
+ initialDecision: null,
42
+ initialRequiresConsent: null,
43
+ resolveWithDecision: (decision)=>({
44
+ consentStatus: decision,
45
+ shouldLoadScripts: true
46
+ }),
47
+ resolveWithoutDecision: (requiresConsent)=>({
48
+ consentStatus: requiresConsent === true ? 'denied' : 'granted',
49
+ shouldLoadScripts: true
50
+ }),
51
+ shouldSkipGeolocation: false
52
+ },
53
+ 'require-consent-before-loading-scripts': {
54
+ initialDecision: null,
55
+ initialRequiresConsent: null,
56
+ resolveWithDecision: (decision)=>({
57
+ consentStatus: decision,
58
+ shouldLoadScripts: decision === 'granted'
59
+ }),
60
+ resolveWithoutDecision: (requiresConsent)=>({
61
+ consentStatus: requiresConsent === false ? 'granted' : 'denied',
62
+ shouldLoadScripts: requiresConsent === false
63
+ }),
64
+ shouldSkipGeolocation: false
65
+ }
66
+ };
13
67
  const getStoredDecision = ()=>{
14
68
  const stored = localStorage.getItem(CONSENT_STORAGE_KEY);
15
69
  if (stored === 'true') {
@@ -20,10 +74,27 @@ const getStoredDecision = ()=>{
20
74
  }
21
75
  return null;
22
76
  };
77
+ const computeConsentState = ({ consentStrategy, requiresConsent, userDecision })=>{
78
+ const strategyConfig = STRATEGY_CONFIG[consentStrategy];
79
+ const hasUserDecision = userDecision !== null;
80
+ const shouldShowBanner = !hasUserDecision && requiresConsent === true;
81
+ const { consentStatus, shouldLoadScripts } = hasUserDecision ? strategyConfig.resolveWithDecision(userDecision) : strategyConfig.resolveWithoutDecision(requiresConsent);
82
+ return {
83
+ consentStatus,
84
+ shouldLoadScripts,
85
+ shouldShowBanner
86
+ };
87
+ };
23
88
  export function CookieBannerProvider({ children, consentApiPath, consentStrategy }) {
24
- const [userDecision, setUserDecision] = useState(null);
25
- const [requiresConsent, setRequiresConsent] = useState(null);
89
+ const strategyConfig = STRATEGY_CONFIG[consentStrategy];
90
+ const [userDecision, setUserDecision] = useState(strategyConfig.initialDecision);
91
+ const [requiresConsent, setRequiresConsent] = useState(strategyConfig.initialRequiresConsent);
26
92
  useEffect(()=>{
93
+ if (strategyConfig.shouldSkipGeolocation) {
94
+ setUserDecision(strategyConfig.initialDecision);
95
+ setRequiresConsent(strategyConfig.initialRequiresConsent);
96
+ return;
97
+ }
27
98
  const storedDecision = getStoredDecision();
28
99
  setUserDecision(storedDecision);
29
100
  if (storedDecision !== null) {
@@ -36,8 +107,9 @@ export function CookieBannerProvider({ children, consentApiPath, consentStrategy
36
107
  method: 'GET'
37
108
  });
38
109
  if (!response.ok) {
39
- // TODO: Just console error
40
- throw new Error('Consent API response not ok');
110
+ // eslint-disable-next-line no-console
111
+ console.error(`Consent API response not ok: ${response.status}, assuming cookie consent is required`);
112
+ throw new Error(`Consent API response not ok: ${response.status}`);
41
113
  }
42
114
  const data = await response.json();
43
115
  if (isActive) {
@@ -54,42 +126,27 @@ export function CookieBannerProvider({ children, consentApiPath, consentStrategy
54
126
  isActive = false;
55
127
  };
56
128
  }, [
57
- consentApiPath
129
+ consentApiPath,
130
+ strategyConfig
58
131
  ]);
59
132
  const value = useMemo(()=>{
60
- const hasUserDecision = userDecision !== null;
61
- const shouldShowBanner = !hasUserDecision && requiresConsent === true;
62
- let shouldLoadScripts = false;
63
- let consentStatus = 'denied';
64
- if (hasUserDecision) {
65
- consentStatus = userDecision;
66
- shouldLoadScripts = consentStrategy === 'require-consent-before-loading-scripts' ? userDecision === 'granted' : true;
67
- } else {
68
- switch(consentStrategy){
69
- case 'load-scripts-revoke-consent-immediately':
70
- shouldLoadScripts = true;
71
- consentStatus = requiresConsent === false ? 'granted' : 'denied';
72
- break;
73
- case 'load-scripts-then-revoke-consent-after-geolocation-check':
74
- shouldLoadScripts = true;
75
- consentStatus = requiresConsent === true ? 'denied' : 'granted';
76
- break;
77
- case 'require-consent-before-loading-scripts':
78
- shouldLoadScripts = requiresConsent === false;
79
- consentStatus = requiresConsent === false ? 'granted' : 'denied';
80
- break;
81
- }
82
- }
133
+ const accept = ()=>{
134
+ setUserDecision('granted');
135
+ localStorage.setItem(CONSENT_STORAGE_KEY, 'true');
136
+ };
137
+ const reject = ()=>{
138
+ setUserDecision('denied');
139
+ localStorage.setItem(CONSENT_STORAGE_KEY, 'false');
140
+ };
141
+ const { consentStatus, shouldLoadScripts, shouldShowBanner } = computeConsentState({
142
+ consentStrategy,
143
+ requiresConsent,
144
+ userDecision
145
+ });
83
146
  return {
84
- accept: ()=>{
85
- setUserDecision('granted');
86
- localStorage.setItem(CONSENT_STORAGE_KEY, 'true');
87
- },
147
+ accept,
88
148
  consentStatus,
89
- reject: ()=>{
90
- setUserDecision('denied');
91
- localStorage.setItem(CONSENT_STORAGE_KEY, 'false');
92
- },
149
+ reject,
93
150
  shouldLoadScripts,
94
151
  shouldShowBanner
95
152
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/CookieBannerProvider.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\n\nimport { createContext, useContext, useEffect, useMemo, useState } from 'react'\n\nexport type ConsentStrategy =\n | 'load-scripts-revoke-consent-immediately'\n | 'load-scripts-then-revoke-consent-after-geolocation-check'\n | 'require-consent-before-loading-scripts'\n\ntype ConsentStatus = 'denied' | 'granted'\n\ninterface CookieBannerContextType {\n accept: () => void\n consentStatus: ConsentStatus\n reject: () => void\n shouldLoadScripts: boolean\n shouldShowBanner: boolean\n}\n\nconst CookieBannerContext = createContext<CookieBannerContextType | undefined>(undefined)\n\nexport function useCookieBanner() {\n const context = useContext(CookieBannerContext)\n if (!context) {\n throw new Error('useCookieBanner must be used within a CookieBannerProvider')\n }\n return context\n}\n\ninterface CookieBannerProviderProps {\n children: ReactNode\n consentApiPath: string\n /**\n * `load-scripts-revoke-consent-immediately`\n * - Render scripts immediately.\n * - Default consent is denied until a user grants.\n * - Banner shown only if geolocation requires consent.\n *\n * `load-scripts-then-revoke-consent-after-geolocation-check`\n * - Render scripts immediately.\n * - Default consent is granted until geolocation requires consent.\n * - If consent is required, revoke and show banner.\n *\n * `require-consent-before-loading-scripts`\n * - Do not render scripts until consent is granted when required.\n * - Banner shown only if geolocation requires consent.\n */\n consentStrategy: ConsentStrategy\n}\n\nconst CONSENT_STORAGE_KEY = 'cookiesAllowed'\n\nconst getStoredDecision = (): ConsentStatus | null => {\n const stored = localStorage.getItem(CONSENT_STORAGE_KEY)\n if (stored === 'true') {\n return 'granted'\n }\n if (stored === 'false') {\n return 'denied'\n }\n return null\n}\n\nexport function CookieBannerProvider({\n children,\n consentApiPath,\n consentStrategy,\n}: CookieBannerProviderProps) {\n const [userDecision, setUserDecision] = useState<ConsentStatus | null>(null)\n const [requiresConsent, setRequiresConsent] = useState<boolean | null>(null)\n\n useEffect(() => {\n const storedDecision = getStoredDecision()\n setUserDecision(storedDecision)\n if (storedDecision !== null) {\n return\n }\n\n let isActive = true\n const fetchRequiresConsent = async () => {\n try {\n const response = await fetch(consentApiPath, { method: 'GET' })\n if (!response.ok) {\n // TODO: Just console error\n throw new Error('Consent API response not ok')\n }\n const data = (await response.json()) as { requiresConsent?: boolean }\n if (isActive) {\n setRequiresConsent(Boolean(data?.requiresConsent))\n }\n } catch {\n if (isActive) {\n setRequiresConsent(true)\n }\n }\n }\n\n void fetchRequiresConsent()\n\n return () => {\n isActive = false\n }\n }, [consentApiPath])\n\n const value = useMemo<CookieBannerContextType>(() => {\n const hasUserDecision = userDecision !== null\n const shouldShowBanner = !hasUserDecision && requiresConsent === true\n\n let shouldLoadScripts = false\n let consentStatus: ConsentStatus = 'denied'\n\n if (hasUserDecision) {\n consentStatus = userDecision\n shouldLoadScripts =\n consentStrategy === 'require-consent-before-loading-scripts'\n ? userDecision === 'granted'\n : true\n } else {\n switch (consentStrategy) {\n case 'load-scripts-revoke-consent-immediately':\n shouldLoadScripts = true\n consentStatus = requiresConsent === false ? 'granted' : 'denied'\n break\n case 'load-scripts-then-revoke-consent-after-geolocation-check':\n shouldLoadScripts = true\n consentStatus = requiresConsent === true ? 'denied' : 'granted'\n break\n case 'require-consent-before-loading-scripts':\n shouldLoadScripts = requiresConsent === false\n consentStatus = requiresConsent === false ? 'granted' : 'denied'\n break\n }\n }\n\n return {\n accept: () => {\n setUserDecision('granted')\n localStorage.setItem(CONSENT_STORAGE_KEY, 'true')\n },\n consentStatus,\n reject: () => {\n setUserDecision('denied')\n localStorage.setItem(CONSENT_STORAGE_KEY, 'false')\n },\n shouldLoadScripts,\n shouldShowBanner,\n }\n }, [consentStrategy, requiresConsent, userDecision])\n\n return <CookieBannerContext.Provider value={value}>{children}</CookieBannerContext.Provider>\n}\n"],"names":["createContext","useContext","useEffect","useMemo","useState","CookieBannerContext","undefined","useCookieBanner","context","Error","CONSENT_STORAGE_KEY","getStoredDecision","stored","localStorage","getItem","CookieBannerProvider","children","consentApiPath","consentStrategy","userDecision","setUserDecision","requiresConsent","setRequiresConsent","storedDecision","isActive","fetchRequiresConsent","response","fetch","method","ok","data","json","Boolean","value","hasUserDecision","shouldShowBanner","shouldLoadScripts","consentStatus","accept","setItem","reject","Provider"],"mappings":"AAAA;;AAIA,SAASA,aAAa,EAAEC,UAAU,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAiB/E,MAAMC,oCAAsBL,cAAmDM;AAE/E,OAAO,SAASC;IACd,MAAMC,UAAUP,WAAWI;IAC3B,IAAI,CAACG,SAAS;QACZ,MAAM,IAAIC,MAAM;IAClB;IACA,OAAOD;AACT;AAuBA,MAAME,sBAAsB;AAE5B,MAAMC,oBAAoB;IACxB,MAAMC,SAASC,aAAaC,OAAO,CAACJ;IACpC,IAAIE,WAAW,QAAQ;QACrB,OAAO;IACT;IACA,IAAIA,WAAW,SAAS;QACtB,OAAO;IACT;IACA,OAAO;AACT;AAEA,OAAO,SAASG,qBAAqB,EACnCC,QAAQ,EACRC,cAAc,EACdC,eAAe,EACW;IAC1B,MAAM,CAACC,cAAcC,gBAAgB,GAAGhB,SAA+B;IACvE,MAAM,CAACiB,iBAAiBC,mBAAmB,GAAGlB,SAAyB;IAEvEF,UAAU;QACR,MAAMqB,iBAAiBZ;QACvBS,gBAAgBG;QAChB,IAAIA,mBAAmB,MAAM;YAC3B;QACF;QAEA,IAAIC,WAAW;QACf,MAAMC,uBAAuB;YAC3B,IAAI;gBACF,MAAMC,WAAW,MAAMC,MAAMV,gBAAgB;oBAAEW,QAAQ;gBAAM;gBAC7D,IAAI,CAACF,SAASG,EAAE,EAAE;oBAChB,2BAA2B;oBAC3B,MAAM,IAAIpB,MAAM;gBAClB;gBACA,MAAMqB,OAAQ,MAAMJ,SAASK,IAAI;gBACjC,IAAIP,UAAU;oBACZF,mBAAmBU,QAAQF,MAAMT;gBACnC;YACF,EAAE,OAAM;gBACN,IAAIG,UAAU;oBACZF,mBAAmB;gBACrB;YACF;QACF;QAEA,KAAKG;QAEL,OAAO;YACLD,WAAW;QACb;IACF,GAAG;QAACP;KAAe;IAEnB,MAAMgB,QAAQ9B,QAAiC;QAC7C,MAAM+B,kBAAkBf,iBAAiB;QACzC,MAAMgB,mBAAmB,CAACD,mBAAmBb,oBAAoB;QAEjE,IAAIe,oBAAoB;QACxB,IAAIC,gBAA+B;QAEnC,IAAIH,iBAAiB;YACnBG,gBAAgBlB;YAChBiB,oBACElB,oBAAoB,2CAChBC,iBAAiB,YACjB;QACR,OAAO;YACL,OAAQD;gBACN,KAAK;oBACHkB,oBAAoB;oBACpBC,gBAAgBhB,oBAAoB,QAAQ,YAAY;oBACxD;gBACF,KAAK;oBACHe,oBAAoB;oBACpBC,gBAAgBhB,oBAAoB,OAAO,WAAW;oBACtD;gBACF,KAAK;oBACHe,oBAAoBf,oBAAoB;oBACxCgB,gBAAgBhB,oBAAoB,QAAQ,YAAY;oBACxD;YACJ;QACF;QAEA,OAAO;YACLiB,QAAQ;gBACNlB,gBAAgB;gBAChBP,aAAa0B,OAAO,CAAC7B,qBAAqB;YAC5C;YACA2B;YACAG,QAAQ;gBACNpB,gBAAgB;gBAChBP,aAAa0B,OAAO,CAAC7B,qBAAqB;YAC5C;YACA0B;YACAD;QACF;IACF,GAAG;QAACjB;QAAiBG;QAAiBF;KAAa;IAEnD,qBAAO,KAACd,oBAAoBoC,QAAQ;QAACR,OAAOA;kBAAQjB;;AACtD"}
1
+ {"version":3,"sources":["../src/CookieBannerProvider.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\n\nimport { createContext, useContext, useEffect, useMemo, useState } from 'react'\n\nexport type ConsentStrategy =\n | 'load-scripts-always-grant-consent'\n | 'load-scripts-revoke-consent-immediately'\n | 'load-scripts-then-revoke-consent-after-geolocation-check'\n | 'require-consent-before-loading-scripts'\n\ntype ConsentStatus = 'denied' | 'granted'\n\ninterface CookieBannerContextType {\n accept: () => void\n consentStatus: ConsentStatus\n reject: () => void\n shouldLoadScripts: boolean\n shouldShowBanner: boolean\n}\n\nconst CookieBannerContext = createContext<CookieBannerContextType | undefined>(undefined)\n\nexport function useCookieBanner() {\n const context = useContext(CookieBannerContext)\n if (!context) {\n throw new Error('useCookieBanner must be used within a CookieBannerProvider')\n }\n return context\n}\n\ninterface CookieBannerProviderProps {\n children: ReactNode\n consentApiPath: string\n /**\n * `load-scripts-revoke-consent-immediately`\n * - Render scripts immediately.\n * - Default consent is denied until a user grants.\n * - Banner shown only if geolocation requires consent.\n *\n * `load-scripts-then-revoke-consent-after-geolocation-check`\n * - Render scripts immediately.\n * - Default consent is granted until geolocation requires consent.\n * - If consent is required, revoke and show banner.\n *\n * `require-consent-before-loading-scripts`\n * - Do not render scripts until consent is granted when required.\n * - Banner shown only if geolocation requires consent.\n *\n * `load-scripts-always-grant-consent`\n * - Render scripts immediately.\n * - Consent is always granted, regardless of geolocation.\n * - Banner is never shown.\n */\n consentStrategy: ConsentStrategy\n}\n\nconst CONSENT_STORAGE_KEY = 'cookiesAllowed'\n\ntype StrategyConfig = {\n initialDecision: ConsentStatus | null\n initialRequiresConsent: boolean | null\n resolveWithDecision: (decision: ConsentStatus) => {\n consentStatus: ConsentStatus\n shouldLoadScripts: boolean\n }\n resolveWithoutDecision: (requiresConsent: boolean | null) => {\n consentStatus: ConsentStatus\n shouldLoadScripts: boolean\n }\n shouldSkipGeolocation: boolean\n}\n\nconst STRATEGY_CONFIG: Record<ConsentStrategy, StrategyConfig> = {\n 'load-scripts-always-grant-consent': {\n initialDecision: 'granted',\n initialRequiresConsent: false,\n resolveWithDecision: () => ({\n consentStatus: 'granted',\n shouldLoadScripts: true,\n }),\n resolveWithoutDecision: () => ({\n consentStatus: 'granted',\n shouldLoadScripts: true,\n }),\n shouldSkipGeolocation: true,\n },\n 'load-scripts-revoke-consent-immediately': {\n initialDecision: null,\n initialRequiresConsent: null,\n resolveWithDecision: (decision) => ({\n consentStatus: decision,\n shouldLoadScripts: true,\n }),\n resolveWithoutDecision: (requiresConsent) => ({\n consentStatus: requiresConsent === false ? 'granted' : 'denied',\n shouldLoadScripts: true,\n }),\n shouldSkipGeolocation: false,\n },\n 'load-scripts-then-revoke-consent-after-geolocation-check': {\n initialDecision: null,\n initialRequiresConsent: null,\n resolveWithDecision: (decision) => ({\n consentStatus: decision,\n shouldLoadScripts: true,\n }),\n resolveWithoutDecision: (requiresConsent) => ({\n consentStatus: requiresConsent === true ? 'denied' : 'granted',\n shouldLoadScripts: true,\n }),\n shouldSkipGeolocation: false,\n },\n 'require-consent-before-loading-scripts': {\n initialDecision: null,\n initialRequiresConsent: null,\n resolveWithDecision: (decision) => ({\n consentStatus: decision,\n shouldLoadScripts: decision === 'granted',\n }),\n resolveWithoutDecision: (requiresConsent) => ({\n consentStatus: requiresConsent === false ? 'granted' : 'denied',\n shouldLoadScripts: requiresConsent === false,\n }),\n shouldSkipGeolocation: false,\n },\n}\n\nconst getStoredDecision = (): ConsentStatus | null => {\n const stored = localStorage.getItem(CONSENT_STORAGE_KEY)\n if (stored === 'true') {\n return 'granted'\n }\n if (stored === 'false') {\n return 'denied'\n }\n return null\n}\n\nconst computeConsentState = ({\n consentStrategy,\n requiresConsent,\n userDecision,\n}: {\n consentStrategy: ConsentStrategy\n requiresConsent: boolean | null\n userDecision: ConsentStatus | null\n}): Pick<CookieBannerContextType, 'consentStatus' | 'shouldLoadScripts' | 'shouldShowBanner'> => {\n const strategyConfig = STRATEGY_CONFIG[consentStrategy]\n const hasUserDecision = userDecision !== null\n const shouldShowBanner = !hasUserDecision && requiresConsent === true\n const { consentStatus, shouldLoadScripts } = hasUserDecision\n ? strategyConfig.resolveWithDecision(userDecision)\n : strategyConfig.resolveWithoutDecision(requiresConsent)\n\n return {\n consentStatus,\n shouldLoadScripts,\n shouldShowBanner,\n }\n}\n\nexport function CookieBannerProvider({\n children,\n consentApiPath,\n consentStrategy,\n}: CookieBannerProviderProps) {\n const strategyConfig = STRATEGY_CONFIG[consentStrategy]\n const [userDecision, setUserDecision] = useState<ConsentStatus | null>(\n strategyConfig.initialDecision,\n )\n const [requiresConsent, setRequiresConsent] = useState<boolean | null>(\n strategyConfig.initialRequiresConsent,\n )\n\n useEffect(() => {\n if (strategyConfig.shouldSkipGeolocation) {\n setUserDecision(strategyConfig.initialDecision)\n setRequiresConsent(strategyConfig.initialRequiresConsent)\n return\n }\n\n const storedDecision = getStoredDecision()\n setUserDecision(storedDecision)\n if (storedDecision !== null) {\n return\n }\n\n let isActive = true\n const fetchRequiresConsent = async () => {\n try {\n const response = await fetch(consentApiPath, { method: 'GET' })\n if (!response.ok) {\n // eslint-disable-next-line no-console\n console.error(\n `Consent API response not ok: ${response.status}, assuming cookie consent is required`,\n )\n throw new Error(`Consent API response not ok: ${response.status}`)\n }\n const data = (await response.json()) as { requiresConsent?: boolean }\n if (isActive) {\n setRequiresConsent(Boolean(data?.requiresConsent))\n }\n } catch {\n if (isActive) {\n setRequiresConsent(true)\n }\n }\n }\n\n void fetchRequiresConsent()\n\n return () => {\n isActive = false\n }\n }, [consentApiPath, strategyConfig])\n\n const value = useMemo<CookieBannerContextType>(() => {\n const accept = () => {\n setUserDecision('granted')\n localStorage.setItem(CONSENT_STORAGE_KEY, 'true')\n }\n const reject = () => {\n setUserDecision('denied')\n localStorage.setItem(CONSENT_STORAGE_KEY, 'false')\n }\n\n const { consentStatus, shouldLoadScripts, shouldShowBanner } = computeConsentState({\n consentStrategy,\n requiresConsent,\n userDecision,\n })\n\n return {\n accept,\n consentStatus,\n reject,\n shouldLoadScripts,\n shouldShowBanner,\n }\n }, [consentStrategy, requiresConsent, userDecision])\n\n return <CookieBannerContext.Provider value={value}>{children}</CookieBannerContext.Provider>\n}\n"],"names":["createContext","useContext","useEffect","useMemo","useState","CookieBannerContext","undefined","useCookieBanner","context","Error","CONSENT_STORAGE_KEY","STRATEGY_CONFIG","initialDecision","initialRequiresConsent","resolveWithDecision","consentStatus","shouldLoadScripts","resolveWithoutDecision","shouldSkipGeolocation","decision","requiresConsent","getStoredDecision","stored","localStorage","getItem","computeConsentState","consentStrategy","userDecision","strategyConfig","hasUserDecision","shouldShowBanner","CookieBannerProvider","children","consentApiPath","setUserDecision","setRequiresConsent","storedDecision","isActive","fetchRequiresConsent","response","fetch","method","ok","console","error","status","data","json","Boolean","value","accept","setItem","reject","Provider"],"mappings":"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"}
@@ -2,11 +2,13 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { usePathname } from 'next/navigation';
4
4
  import Script from 'next/script';
5
- import { useCallback, useEffect } from 'react';
5
+ import { useCallback, useEffect, useRef } from 'react';
6
6
  import { useCookieBanner } from './CookieBannerProvider.js';
7
7
  export default function FacebookPixel({ pixelId }) {
8
8
  const pathname = usePathname();
9
9
  const { consentStatus, shouldLoadScripts } = useCookieBanner();
10
+ const hasSentInitialRef = useRef(false);
11
+ const lastTrackedPathnameRef = useRef(null);
10
12
  const trackPageView = useCallback(()=>{
11
13
  if (typeof window.fbq === 'function') {
12
14
  window.fbq('track', 'PageView');
@@ -29,13 +31,27 @@ export default function FacebookPixel({ pixelId }) {
29
31
  consentStatus
30
32
  ]);
31
33
  useEffect(()=>{
32
- if (pixelId && consentStatus === 'granted') {
34
+ if (!pixelId || consentStatus !== 'granted') {
35
+ return;
36
+ }
37
+ if (typeof window.fbq !== 'function') {
38
+ return;
39
+ }
40
+ if (!hasSentInitialRef.current) {
33
41
  trackPageView();
42
+ hasSentInitialRef.current = true;
43
+ lastTrackedPathnameRef.current = pathname;
44
+ return;
45
+ }
46
+ if (lastTrackedPathnameRef.current !== pathname) {
47
+ trackPageView();
48
+ lastTrackedPathnameRef.current = pathname;
34
49
  }
35
- // eslint-disable-next-line react-hooks/exhaustive-deps
36
50
  }, [
37
51
  pathname,
38
- consentStatus
52
+ consentStatus,
53
+ pixelId,
54
+ trackPageView
39
55
  ]);
40
56
  if (!pixelId || !shouldLoadScripts) {
41
57
  return null;
@@ -53,14 +69,17 @@ export default function FacebookPixel({ pixelId }) {
53
69
  t.src=v;s=b.getElementsByTagName(e)[0];
54
70
  s.parentNode.insertBefore(t,s)}(window, document,'script',
55
71
  'https://connect.facebook.net/en_US/fbevents.js');
72
+ fbq('consent', '${consentStatus === 'granted' ? 'grant' : 'revoke'}');
56
73
  fbq('init', '${pixelId}');
57
74
  `
58
75
  },
59
76
  id: "fb-pixel",
60
77
  onLoad: ()=>{
61
78
  applyConsent(consentStatus);
62
- if (consentStatus === 'granted') {
79
+ if (consentStatus === 'granted' && !hasSentInitialRef.current) {
63
80
  trackPageView();
81
+ hasSentInitialRef.current = true;
82
+ lastTrackedPathnameRef.current = window.location.pathname;
64
83
  }
65
84
  },
66
85
  strategy: "afterInteractive"
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/FacebookPixel.tsx"],"sourcesContent":["'use client'\nimport { usePathname } from 'next/navigation'\nimport Script from 'next/script'\nimport { useCallback, useEffect } from 'react'\n\nimport { useCookieBanner } from './CookieBannerProvider.js'\n\ninterface FacebookPixelProps {\n pixelId: string\n}\n\nexport default function FacebookPixel({ pixelId }: FacebookPixelProps) {\n const pathname = usePathname()\n const { consentStatus, shouldLoadScripts } = useCookieBanner()\n\n const trackPageView = useCallback(() => {\n if (typeof window.fbq === 'function') {\n window.fbq('track', 'PageView')\n }\n }, [])\n\n const applyConsent = useCallback((status: 'denied' | 'granted') => {\n if (typeof window.fbq !== 'function') {\n return\n }\n\n if (status === 'granted') {\n window.fbq('consent', 'grant')\n } else {\n window.fbq('consent', 'revoke')\n }\n }, [])\n\n useEffect(() => {\n applyConsent(consentStatus)\n }, [applyConsent, consentStatus])\n\n useEffect(() => {\n if (pixelId && consentStatus === 'granted') {\n trackPageView()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pathname, consentStatus])\n\n if (!pixelId || !shouldLoadScripts) {\n return null\n }\n\n return (\n <>\n <Script\n dangerouslySetInnerHTML={{\n __html: `\n !function(f,b,e,v,n,t,s)\n {if(f.fbq)return;n=f.fbq=function(){n.callMethod?\n n.callMethod.apply(n,arguments):n.queue.push(arguments)};\n if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';\n n.queue=[];t=b.createElement(e);t.async=!0;\n t.src=v;s=b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t,s)}(window, document,'script',\n 'https://connect.facebook.net/en_US/fbevents.js');\n fbq('init', '${pixelId}');\n `,\n }}\n id=\"fb-pixel\"\n onLoad={() => {\n applyConsent(consentStatus)\n if (consentStatus === 'granted') {\n trackPageView()\n }\n }}\n strategy=\"afterInteractive\"\n />\n {consentStatus === 'granted' && (\n <noscript>\n <img\n alt=\"\"\n height=\"1\"\n loading=\"lazy\"\n src={`https://www.facebook.com/tr?id=${pixelId}&ev=PageView&noscript=1`}\n style={{ display: 'none' }}\n width=\"1\"\n />\n </noscript>\n )}\n </>\n )\n}\n\ndeclare global {\n interface Window {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n fbq: (...args: any[]) => void\n }\n}\n"],"names":["usePathname","Script","useCallback","useEffect","useCookieBanner","FacebookPixel","pixelId","pathname","consentStatus","shouldLoadScripts","trackPageView","window","fbq","applyConsent","status","dangerouslySetInnerHTML","__html","id","onLoad","strategy","noscript","img","alt","height","loading","src","style","display","width"],"mappings":"AAAA;;AACA,SAASA,WAAW,QAAQ,kBAAiB;AAC7C,OAAOC,YAAY,cAAa;AAChC,SAASC,WAAW,EAAEC,SAAS,QAAQ,QAAO;AAE9C,SAASC,eAAe,QAAQ,4BAA2B;AAM3D,eAAe,SAASC,cAAc,EAAEC,OAAO,EAAsB;IACnE,MAAMC,WAAWP;IACjB,MAAM,EAAEQ,aAAa,EAAEC,iBAAiB,EAAE,GAAGL;IAE7C,MAAMM,gBAAgBR,YAAY;QAChC,IAAI,OAAOS,OAAOC,GAAG,KAAK,YAAY;YACpCD,OAAOC,GAAG,CAAC,SAAS;QACtB;IACF,GAAG,EAAE;IAEL,MAAMC,eAAeX,YAAY,CAACY;QAChC,IAAI,OAAOH,OAAOC,GAAG,KAAK,YAAY;YACpC;QACF;QAEA,IAAIE,WAAW,WAAW;YACxBH,OAAOC,GAAG,CAAC,WAAW;QACxB,OAAO;YACLD,OAAOC,GAAG,CAAC,WAAW;QACxB;IACF,GAAG,EAAE;IAELT,UAAU;QACRU,aAAaL;IACf,GAAG;QAACK;QAAcL;KAAc;IAEhCL,UAAU;QACR,IAAIG,WAAWE,kBAAkB,WAAW;YAC1CE;QACF;IACA,uDAAuD;IACzD,GAAG;QAACH;QAAUC;KAAc;IAE5B,IAAI,CAACF,WAAW,CAACG,mBAAmB;QAClC,OAAO;IACT;IAEA,qBACE;;0BACE,KAACR;gBACCc,yBAAyB;oBACvBC,QAAQ,CAAC;;;;;;;;;yBASM,EAAEV,QAAQ;UACzB,CAAC;gBACH;gBACAW,IAAG;gBACHC,QAAQ;oBACNL,aAAaL;oBACb,IAAIA,kBAAkB,WAAW;wBAC/BE;oBACF;gBACF;gBACAS,UAAS;;YAEVX,kBAAkB,2BACjB,KAACY;0BACC,cAAA,KAACC;oBACCC,KAAI;oBACJC,QAAO;oBACPC,SAAQ;oBACRC,KAAK,CAAC,+BAA+B,EAAEnB,QAAQ,uBAAuB,CAAC;oBACvEoB,OAAO;wBAAEC,SAAS;oBAAO;oBACzBC,OAAM;;;;;AAMlB"}
1
+ {"version":3,"sources":["../src/FacebookPixel.tsx"],"sourcesContent":["'use client'\nimport { usePathname } from 'next/navigation'\nimport Script from 'next/script'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport { useCookieBanner } from './CookieBannerProvider.js'\n\ninterface FacebookPixelProps {\n pixelId: string\n}\n\nexport default function FacebookPixel({ pixelId }: FacebookPixelProps) {\n const pathname = usePathname()\n const { consentStatus, shouldLoadScripts } = useCookieBanner()\n const hasSentInitialRef = useRef(false)\n const lastTrackedPathnameRef = useRef<null | string>(null)\n\n const trackPageView = useCallback(() => {\n if (typeof window.fbq === 'function') {\n window.fbq('track', 'PageView')\n }\n }, [])\n\n const applyConsent = useCallback((status: 'denied' | 'granted') => {\n if (typeof window.fbq !== 'function') {\n return\n }\n\n if (status === 'granted') {\n window.fbq('consent', 'grant')\n } else {\n window.fbq('consent', 'revoke')\n }\n }, [])\n\n useEffect(() => {\n applyConsent(consentStatus)\n }, [applyConsent, consentStatus])\n\n useEffect(() => {\n if (!pixelId || consentStatus !== 'granted') {\n return\n }\n if (typeof window.fbq !== 'function') {\n return\n }\n if (!hasSentInitialRef.current) {\n trackPageView()\n hasSentInitialRef.current = true\n lastTrackedPathnameRef.current = pathname\n return\n }\n if (lastTrackedPathnameRef.current !== pathname) {\n trackPageView()\n lastTrackedPathnameRef.current = pathname\n }\n }, [pathname, consentStatus, pixelId, trackPageView])\n\n if (!pixelId || !shouldLoadScripts) {\n return null\n }\n\n return (\n <>\n <Script\n dangerouslySetInnerHTML={{\n __html: `\n !function(f,b,e,v,n,t,s)\n {if(f.fbq)return;n=f.fbq=function(){n.callMethod?\n n.callMethod.apply(n,arguments):n.queue.push(arguments)};\n if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';\n n.queue=[];t=b.createElement(e);t.async=!0;\n t.src=v;s=b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t,s)}(window, document,'script',\n 'https://connect.facebook.net/en_US/fbevents.js');\n fbq('consent', '${consentStatus === 'granted' ? 'grant' : 'revoke'}');\n fbq('init', '${pixelId}');\n `,\n }}\n id=\"fb-pixel\"\n onLoad={() => {\n applyConsent(consentStatus)\n if (consentStatus === 'granted' && !hasSentInitialRef.current) {\n trackPageView()\n hasSentInitialRef.current = true\n lastTrackedPathnameRef.current = window.location.pathname\n }\n }}\n strategy=\"afterInteractive\"\n />\n {consentStatus === 'granted' && (\n <noscript>\n <img\n alt=\"\"\n height=\"1\"\n loading=\"lazy\"\n src={`https://www.facebook.com/tr?id=${pixelId}&ev=PageView&noscript=1`}\n style={{ display: 'none' }}\n width=\"1\"\n />\n </noscript>\n )}\n </>\n )\n}\n\ndeclare global {\n interface Window {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n fbq: (...args: any[]) => void\n }\n}\n"],"names":["usePathname","Script","useCallback","useEffect","useRef","useCookieBanner","FacebookPixel","pixelId","pathname","consentStatus","shouldLoadScripts","hasSentInitialRef","lastTrackedPathnameRef","trackPageView","window","fbq","applyConsent","status","current","dangerouslySetInnerHTML","__html","id","onLoad","location","strategy","noscript","img","alt","height","loading","src","style","display","width"],"mappings":"AAAA;;AACA,SAASA,WAAW,QAAQ,kBAAiB;AAC7C,OAAOC,YAAY,cAAa;AAChC,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAEtD,SAASC,eAAe,QAAQ,4BAA2B;AAM3D,eAAe,SAASC,cAAc,EAAEC,OAAO,EAAsB;IACnE,MAAMC,WAAWR;IACjB,MAAM,EAAES,aAAa,EAAEC,iBAAiB,EAAE,GAAGL;IAC7C,MAAMM,oBAAoBP,OAAO;IACjC,MAAMQ,yBAAyBR,OAAsB;IAErD,MAAMS,gBAAgBX,YAAY;QAChC,IAAI,OAAOY,OAAOC,GAAG,KAAK,YAAY;YACpCD,OAAOC,GAAG,CAAC,SAAS;QACtB;IACF,GAAG,EAAE;IAEL,MAAMC,eAAed,YAAY,CAACe;QAChC,IAAI,OAAOH,OAAOC,GAAG,KAAK,YAAY;YACpC;QACF;QAEA,IAAIE,WAAW,WAAW;YACxBH,OAAOC,GAAG,CAAC,WAAW;QACxB,OAAO;YACLD,OAAOC,GAAG,CAAC,WAAW;QACxB;IACF,GAAG,EAAE;IAELZ,UAAU;QACRa,aAAaP;IACf,GAAG;QAACO;QAAcP;KAAc;IAEhCN,UAAU;QACR,IAAI,CAACI,WAAWE,kBAAkB,WAAW;YAC3C;QACF;QACA,IAAI,OAAOK,OAAOC,GAAG,KAAK,YAAY;YACpC;QACF;QACA,IAAI,CAACJ,kBAAkBO,OAAO,EAAE;YAC9BL;YACAF,kBAAkBO,OAAO,GAAG;YAC5BN,uBAAuBM,OAAO,GAAGV;YACjC;QACF;QACA,IAAII,uBAAuBM,OAAO,KAAKV,UAAU;YAC/CK;YACAD,uBAAuBM,OAAO,GAAGV;QACnC;IACF,GAAG;QAACA;QAAUC;QAAeF;QAASM;KAAc;IAEpD,IAAI,CAACN,WAAW,CAACG,mBAAmB;QAClC,OAAO;IACT;IAEA,qBACE;;0BACE,KAACT;gBACCkB,yBAAyB;oBACvBC,QAAQ,CAAC;;;;;;;;;4BASS,EAAEX,kBAAkB,YAAY,UAAU,SAAS;yBACtD,EAAEF,QAAQ;UACzB,CAAC;gBACH;gBACAc,IAAG;gBACHC,QAAQ;oBACNN,aAAaP;oBACb,IAAIA,kBAAkB,aAAa,CAACE,kBAAkBO,OAAO,EAAE;wBAC7DL;wBACAF,kBAAkBO,OAAO,GAAG;wBAC5BN,uBAAuBM,OAAO,GAAGJ,OAAOS,QAAQ,CAACf,QAAQ;oBAC3D;gBACF;gBACAgB,UAAS;;YAEVf,kBAAkB,2BACjB,KAACgB;0BACC,cAAA,KAACC;oBACCC,KAAI;oBACJC,QAAO;oBACPC,SAAQ;oBACRC,KAAK,CAAC,+BAA+B,EAAEvB,QAAQ,uBAAuB,CAAC;oBACvEwB,OAAO;wBAAEC,SAAS;oBAAO;oBACzBC,OAAM;;;;;AAMlB"}
@@ -2,13 +2,14 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { usePathname } from 'next/navigation';
4
4
  import Script from 'next/script';
5
- import { useCallback, useEffect, useRef } from 'react';
5
+ import { useCallback, useEffect, useRef, useState } from 'react';
6
6
  import { useCookieBanner } from './CookieBannerProvider.js';
7
7
  export default function GoogleAnalytics({ gaId }) {
8
8
  const pathname = usePathname();
9
9
  const { consentStatus, shouldLoadScripts } = useCookieBanner();
10
10
  const hasSentInitialRef = useRef(false);
11
11
  const lastTrackedPathnameRef = useRef(null);
12
+ const [isGtagLoaded, setIsGtagLoaded] = useState(false);
12
13
  const trackPageView = useCallback(()=>{
13
14
  if (typeof window.gtag === 'function') {
14
15
  window.gtag('config', gaId, {
@@ -73,7 +74,7 @@ export default function GoogleAnalytics({ gaId }) {
73
74
  updateConsent
74
75
  ]);
75
76
  useEffect(()=>{
76
- if (consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {
77
+ if (isGtagLoaded && consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {
77
78
  if (!hasSentInitialRef.current) {
78
79
  window.gtag('config', gaId, {
79
80
  page_path: window.location.pathname
@@ -84,10 +85,11 @@ export default function GoogleAnalytics({ gaId }) {
84
85
  }
85
86
  }, [
86
87
  consentStatus,
87
- gaId
88
+ gaId,
89
+ isGtagLoaded
88
90
  ]);
89
91
  useEffect(()=>{
90
- if (consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {
92
+ if (isGtagLoaded && consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {
91
93
  if (hasSentInitialRef.current) {
92
94
  if (lastTrackedPathnameRef.current !== pathname) {
93
95
  trackPageView();
@@ -99,7 +101,8 @@ export default function GoogleAnalytics({ gaId }) {
99
101
  pathname,
100
102
  consentStatus,
101
103
  gaId,
102
- trackPageView
104
+ trackPageView,
105
+ isGtagLoaded
103
106
  ]);
104
107
  if (!gaId || !shouldLoadScripts) {
105
108
  return null;
@@ -120,6 +123,9 @@ export default function GoogleAnalytics({ gaId }) {
120
123
  }),
121
124
  /*#__PURE__*/ _jsx(Script, {
122
125
  id: "gtag-base",
126
+ onLoad: ()=>{
127
+ setIsGtagLoaded(true);
128
+ },
123
129
  src: `https://www.googletagmanager.com/gtag/js?id=${gaId}`,
124
130
  strategy: "afterInteractive"
125
131
  }),
@@ -133,18 +139,6 @@ export default function GoogleAnalytics({ gaId }) {
133
139
  `
134
140
  },
135
141
  id: "gtag-init",
136
- onLoad: ()=>{
137
- if (typeof window.gtag === 'function') {
138
- updateConsent(consentStatus);
139
- if (consentStatus === 'granted') {
140
- window.gtag('config', gaId, {
141
- page_path: window.location.pathname
142
- });
143
- hasSentInitialRef.current = true;
144
- lastTrackedPathnameRef.current = window.location.pathname;
145
- }
146
- }
147
- },
148
142
  strategy: "afterInteractive"
149
143
  })
150
144
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/GoogleAnalytics.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n'use client'\n\nimport { usePathname } from 'next/navigation'\nimport Script from 'next/script'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport { useCookieBanner } from './CookieBannerProvider.js'\n\ninterface GoogleAnalyticsProps {\n gaId: string\n}\n\nexport default function GoogleAnalytics({ gaId }: GoogleAnalyticsProps) {\n const pathname = usePathname()\n const { consentStatus, shouldLoadScripts } = useCookieBanner()\n const hasSentInitialRef = useRef(false)\n const lastTrackedPathnameRef = useRef<null | string>(null)\n\n const trackPageView = useCallback(() => {\n if (typeof window.gtag === 'function') {\n window.gtag('config', gaId, {\n page_path: pathname,\n })\n }\n }, [gaId, pathname])\n\n const updateConsent = useCallback((status: 'denied' | 'granted') => {\n if (typeof window.gtag !== 'function') {\n return\n }\n\n if (status === 'granted') {\n window.gtag('consent', 'update', {\n ad_personalization: 'granted',\n ad_storage: 'granted',\n ad_user_data: 'granted',\n analytics_storage: 'granted',\n functionality_storage: 'granted',\n personalization_storage: 'granted',\n })\n } else {\n window.gtag('consent', 'update', {\n ad_personalization: 'denied',\n ad_storage: 'denied',\n ad_user_data: 'denied',\n analytics_storage: 'denied',\n functionality_storage: 'denied',\n personalization_storage: 'denied',\n security_storage: 'granted',\n })\n }\n }, [])\n\n const getConsentDefaults = useCallback((status: 'denied' | 'granted') => {\n if (status === 'granted') {\n return {\n ad_personalization: 'granted',\n ad_storage: 'granted',\n ad_user_data: 'granted',\n analytics_storage: 'granted',\n functionality_storage: 'granted',\n personalization_storage: 'granted',\n security_storage: 'granted',\n }\n }\n\n return {\n ad_personalization: 'denied',\n ad_storage: 'denied',\n ad_user_data: 'denied',\n analytics_storage: 'denied',\n functionality_storage: 'denied',\n personalization_storage: 'denied',\n security_storage: 'granted',\n }\n }, [])\n\n useEffect(() => {\n updateConsent(consentStatus)\n }, [consentStatus, updateConsent])\n\n useEffect(() => {\n if (consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {\n if (!hasSentInitialRef.current) {\n window.gtag('config', gaId, { page_path: window.location.pathname })\n hasSentInitialRef.current = true\n lastTrackedPathnameRef.current = window.location.pathname\n }\n }\n }, [consentStatus, gaId])\n\n useEffect(() => {\n if (consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {\n if (hasSentInitialRef.current) {\n if (lastTrackedPathnameRef.current !== pathname) {\n trackPageView()\n lastTrackedPathnameRef.current = pathname\n }\n }\n }\n }, [pathname, consentStatus, gaId, trackPageView])\n\n if (!gaId || !shouldLoadScripts) {\n return null\n }\n\n return (\n <>\n <Script\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n window.gtag = window.gtag || gtag;\n gtag('consent', 'default', ${JSON.stringify(getConsentDefaults(consentStatus))});\n `,\n }}\n id=\"ga-consent-default\"\n strategy=\"beforeInteractive\"\n />\n <Script\n id=\"gtag-base\"\n src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}\n strategy=\"afterInteractive\"\n />\n <Script\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);} \n window.gtag = gtag;\n gtag('js', new Date());\n `,\n }}\n id=\"gtag-init\"\n onLoad={() => {\n if (typeof window.gtag === 'function') {\n updateConsent(consentStatus)\n if (consentStatus === 'granted') {\n window.gtag('config', gaId, { page_path: window.location.pathname })\n hasSentInitialRef.current = true\n lastTrackedPathnameRef.current = window.location.pathname\n }\n }\n }}\n strategy=\"afterInteractive\"\n />\n </>\n )\n}\n\ndeclare global {\n interface Window {\n dataLayer: any[]\n gtag: (...args: any[]) => void\n }\n}\n"],"names":["usePathname","Script","useCallback","useEffect","useRef","useCookieBanner","GoogleAnalytics","gaId","pathname","consentStatus","shouldLoadScripts","hasSentInitialRef","lastTrackedPathnameRef","trackPageView","window","gtag","page_path","updateConsent","status","ad_personalization","ad_storage","ad_user_data","analytics_storage","functionality_storage","personalization_storage","security_storage","getConsentDefaults","current","location","dangerouslySetInnerHTML","__html","JSON","stringify","id","strategy","src","onLoad"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/GoogleAnalytics.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n'use client'\n\nimport { usePathname } from 'next/navigation'\nimport Script from 'next/script'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { useCookieBanner } from './CookieBannerProvider.js'\n\ninterface GoogleAnalyticsProps {\n gaId: string\n}\n\nexport default function GoogleAnalytics({ gaId }: GoogleAnalyticsProps) {\n const pathname = usePathname()\n const { consentStatus, shouldLoadScripts } = useCookieBanner()\n const hasSentInitialRef = useRef(false)\n const lastTrackedPathnameRef = useRef<null | string>(null)\n const [isGtagLoaded, setIsGtagLoaded] = useState(false)\n\n const trackPageView = useCallback(() => {\n if (typeof window.gtag === 'function') {\n window.gtag('config', gaId, {\n page_path: pathname,\n })\n }\n }, [gaId, pathname])\n\n const updateConsent = useCallback((status: 'denied' | 'granted') => {\n if (typeof window.gtag !== 'function') {\n return\n }\n\n if (status === 'granted') {\n window.gtag('consent', 'update', {\n ad_personalization: 'granted',\n ad_storage: 'granted',\n ad_user_data: 'granted',\n analytics_storage: 'granted',\n functionality_storage: 'granted',\n personalization_storage: 'granted',\n })\n } else {\n window.gtag('consent', 'update', {\n ad_personalization: 'denied',\n ad_storage: 'denied',\n ad_user_data: 'denied',\n analytics_storage: 'denied',\n functionality_storage: 'denied',\n personalization_storage: 'denied',\n security_storage: 'granted',\n })\n }\n }, [])\n\n const getConsentDefaults = useCallback((status: 'denied' | 'granted') => {\n if (status === 'granted') {\n return {\n ad_personalization: 'granted',\n ad_storage: 'granted',\n ad_user_data: 'granted',\n analytics_storage: 'granted',\n functionality_storage: 'granted',\n personalization_storage: 'granted',\n security_storage: 'granted',\n }\n }\n\n return {\n ad_personalization: 'denied',\n ad_storage: 'denied',\n ad_user_data: 'denied',\n analytics_storage: 'denied',\n functionality_storage: 'denied',\n personalization_storage: 'denied',\n security_storage: 'granted',\n }\n }, [])\n\n useEffect(() => {\n updateConsent(consentStatus)\n }, [consentStatus, updateConsent])\n\n useEffect(() => {\n if (isGtagLoaded && consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {\n if (!hasSentInitialRef.current) {\n window.gtag('config', gaId, { page_path: window.location.pathname })\n hasSentInitialRef.current = true\n lastTrackedPathnameRef.current = window.location.pathname\n }\n }\n }, [consentStatus, gaId, isGtagLoaded])\n\n useEffect(() => {\n if (isGtagLoaded && consentStatus === 'granted' && gaId && typeof window.gtag === 'function') {\n if (hasSentInitialRef.current) {\n if (lastTrackedPathnameRef.current !== pathname) {\n trackPageView()\n lastTrackedPathnameRef.current = pathname\n }\n }\n }\n }, [pathname, consentStatus, gaId, trackPageView, isGtagLoaded])\n\n if (!gaId || !shouldLoadScripts) {\n return null\n }\n\n return (\n <>\n <Script\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n window.gtag = window.gtag || gtag;\n gtag('consent', 'default', ${JSON.stringify(getConsentDefaults(consentStatus))});\n `,\n }}\n id=\"ga-consent-default\"\n strategy=\"beforeInteractive\"\n />\n <Script\n id=\"gtag-base\"\n onLoad={() => {\n setIsGtagLoaded(true)\n }}\n src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}\n strategy=\"afterInteractive\"\n />\n <Script\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);} \n window.gtag = gtag;\n gtag('js', new Date());\n `,\n }}\n id=\"gtag-init\"\n strategy=\"afterInteractive\"\n />\n </>\n )\n}\n\ndeclare global {\n interface Window {\n dataLayer: any[]\n gtag: (...args: any[]) => void\n }\n}\n"],"names":["usePathname","Script","useCallback","useEffect","useRef","useState","useCookieBanner","GoogleAnalytics","gaId","pathname","consentStatus","shouldLoadScripts","hasSentInitialRef","lastTrackedPathnameRef","isGtagLoaded","setIsGtagLoaded","trackPageView","window","gtag","page_path","updateConsent","status","ad_personalization","ad_storage","ad_user_data","analytics_storage","functionality_storage","personalization_storage","security_storage","getConsentDefaults","current","location","dangerouslySetInnerHTML","__html","JSON","stringify","id","strategy","onLoad","src"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatworks/analytics",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Analytics components for Next.js with cookie consent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,9 +45,9 @@
45
45
  "vite-tsconfig-paths": "^5.1.4"
46
46
  },
47
47
  "peerDependencies": {
48
- "next": "^15.4.4",
49
- "react": "^19.1.0",
50
- "react-dom": "^19.1.0"
48
+ "next": ">=13.4.0 <16",
49
+ "react": ">=18.2.0 <20",
50
+ "react-dom": ">=18.2.0 <20"
51
51
  },
52
52
  "engines": {
53
53
  "node": "^18.20.2 || >=20.9.0",