@whatworks/analytics 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whatworks/analytics",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|