@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.
- package/dist/CookieBanner.js +1 -1
- package/dist/CookieBanner.js.map +1 -1
- package/dist/CookieBannerProvider.d.ts +6 -1
- package/dist/CookieBannerProvider.js +93 -36
- package/dist/CookieBannerProvider.js.map +1 -1
- package/dist/FacebookPixel.js +24 -5
- package/dist/FacebookPixel.js.map +1 -1
- package/dist/GoogleAnalytics.js +11 -17
- package/dist/GoogleAnalytics.js.map +1 -1
- package/package.json +4 -4
package/dist/CookieBanner.js
CHANGED
|
@@ -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
|
-
|
|
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",
|
package/dist/CookieBanner.js.map
CHANGED
|
@@ -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
|
|
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
|
|
25
|
-
const [
|
|
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
|
-
//
|
|
40
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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":"AAAA;;AAIA,SAASA,aAAa,EAAEC,UAAU,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAkB/E,MAAMC,oCAAsBL,cAAmDM;AAE/E,OAAO,SAASC;IACd,MAAMC,UAAUP,WAAWI;IAC3B,IAAI,CAACG,SAAS;QACZ,MAAM,IAAIC,MAAM;IAClB;IACA,OAAOD;AACT;AA4BA,MAAME,sBAAsB;AAgB5B,MAAMC,kBAA2D;IAC/D,qCAAqC;QACnCC,iBAAiB;QACjBC,wBAAwB;QACxBC,qBAAqB,IAAO,CAAA;gBAC1BC,eAAe;gBACfC,mBAAmB;YACrB,CAAA;QACAC,wBAAwB,IAAO,CAAA;gBAC7BF,eAAe;gBACfC,mBAAmB;YACrB,CAAA;QACAE,uBAAuB;IACzB;IACA,2CAA2C;QACzCN,iBAAiB;QACjBC,wBAAwB;QACxBC,qBAAqB,CAACK,WAAc,CAAA;gBAClCJ,eAAeI;gBACfH,mBAAmB;YACrB,CAAA;QACAC,wBAAwB,CAACG,kBAAqB,CAAA;gBAC5CL,eAAeK,oBAAoB,QAAQ,YAAY;gBACvDJ,mBAAmB;YACrB,CAAA;QACAE,uBAAuB;IACzB;IACA,4DAA4D;QAC1DN,iBAAiB;QACjBC,wBAAwB;QACxBC,qBAAqB,CAACK,WAAc,CAAA;gBAClCJ,eAAeI;gBACfH,mBAAmB;YACrB,CAAA;QACAC,wBAAwB,CAACG,kBAAqB,CAAA;gBAC5CL,eAAeK,oBAAoB,OAAO,WAAW;gBACrDJ,mBAAmB;YACrB,CAAA;QACAE,uBAAuB;IACzB;IACA,0CAA0C;QACxCN,iBAAiB;QACjBC,wBAAwB;QACxBC,qBAAqB,CAACK,WAAc,CAAA;gBAClCJ,eAAeI;gBACfH,mBAAmBG,aAAa;YAClC,CAAA;QACAF,wBAAwB,CAACG,kBAAqB,CAAA;gBAC5CL,eAAeK,oBAAoB,QAAQ,YAAY;gBACvDJ,mBAAmBI,oBAAoB;YACzC,CAAA;QACAF,uBAAuB;IACzB;AACF;AAEA,MAAMG,oBAAoB;IACxB,MAAMC,SAASC,aAAaC,OAAO,CAACd;IACpC,IAAIY,WAAW,QAAQ;QACrB,OAAO;IACT;IACA,IAAIA,WAAW,SAAS;QACtB,OAAO;IACT;IACA,OAAO;AACT;AAEA,MAAMG,sBAAsB,CAAC,EAC3BC,eAAe,EACfN,eAAe,EACfO,YAAY,EAKb;IACC,MAAMC,iBAAiBjB,eAAe,CAACe,gBAAgB;IACvD,MAAMG,kBAAkBF,iBAAiB;IACzC,MAAMG,mBAAmB,CAACD,mBAAmBT,oBAAoB;IACjE,MAAM,EAAEL,aAAa,EAAEC,iBAAiB,EAAE,GAAGa,kBACzCD,eAAed,mBAAmB,CAACa,gBACnCC,eAAeX,sBAAsB,CAACG;IAE1C,OAAO;QACLL;QACAC;QACAc;IACF;AACF;AAEA,OAAO,SAASC,qBAAqB,EACnCC,QAAQ,EACRC,cAAc,EACdP,eAAe,EACW;IAC1B,MAAME,iBAAiBjB,eAAe,CAACe,gBAAgB;IACvD,MAAM,CAACC,cAAcO,gBAAgB,GAAG9B,SACtCwB,eAAehB,eAAe;IAEhC,MAAM,CAACQ,iBAAiBe,mBAAmB,GAAG/B,SAC5CwB,eAAef,sBAAsB;IAGvCX,UAAU;QACR,IAAI0B,eAAeV,qBAAqB,EAAE;YACxCgB,gBAAgBN,eAAehB,eAAe;YAC9CuB,mBAAmBP,eAAef,sBAAsB;YACxD;QACF;QAEA,MAAMuB,iBAAiBf;QACvBa,gBAAgBE;QAChB,IAAIA,mBAAmB,MAAM;YAC3B;QACF;QAEA,IAAIC,WAAW;QACf,MAAMC,uBAAuB;YAC3B,IAAI;gBACF,MAAMC,WAAW,MAAMC,MAAMP,gBAAgB;oBAAEQ,QAAQ;gBAAM;gBAC7D,IAAI,CAACF,SAASG,EAAE,EAAE;oBAChB,sCAAsC;oBACtCC,QAAQC,KAAK,CACX,CAAC,6BAA6B,EAAEL,SAASM,MAAM,CAAC,qCAAqC,CAAC;oBAExF,MAAM,IAAIpC,MAAM,CAAC,6BAA6B,EAAE8B,SAASM,MAAM,EAAE;gBACnE;gBACA,MAAMC,OAAQ,MAAMP,SAASQ,IAAI;gBACjC,IAAIV,UAAU;oBACZF,mBAAmBa,QAAQF,MAAM1B;gBACnC;YACF,EAAE,OAAM;gBACN,IAAIiB,UAAU;oBACZF,mBAAmB;gBACrB;YACF;QACF;QAEA,KAAKG;QAEL,OAAO;YACLD,WAAW;QACb;IACF,GAAG;QAACJ;QAAgBL;KAAe;IAEnC,MAAMqB,QAAQ9C,QAAiC;QAC7C,MAAM+C,SAAS;YACbhB,gBAAgB;YAChBX,aAAa4B,OAAO,CAACzC,qBAAqB;QAC5C;QACA,MAAM0C,SAAS;YACblB,gBAAgB;YAChBX,aAAa4B,OAAO,CAACzC,qBAAqB;QAC5C;QAEA,MAAM,EAAEK,aAAa,EAAEC,iBAAiB,EAAEc,gBAAgB,EAAE,GAAGL,oBAAoB;YACjFC;YACAN;YACAO;QACF;QAEA,OAAO;YACLuB;YACAnC;YACAqC;YACApC;YACAc;QACF;IACF,GAAG;QAACJ;QAAiBN;QAAiBO;KAAa;IAEnD,qBAAO,KAACtB,oBAAoBgD,QAAQ;QAACJ,OAAOA;kBAAQjB;;AACtD"}
|
package/dist/FacebookPixel.js
CHANGED
|
@@ -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
|
|
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
|
|
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"}
|
package/dist/GoogleAnalytics.js
CHANGED
|
@@ -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
|
|
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.
|
|
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": "
|
|
49
|
-
"react": "
|
|
50
|
-
"react-dom": "
|
|
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",
|