nextjs-cookie-consent 1.0.1 → 1.0.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"CookieBanner.d.ts","sourceRoot":"","sources":["../../src/CookieBanner.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmB,MAAM,OAAO,CAAC;AACxC,OAAO,EAAgC,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAuD1E,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAsH7C,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"CookieBanner.d.ts","sourceRoot":"","sources":["../../src/CookieBanner.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAO,EAAgC,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAwD1E,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAkJ7C,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState } from 'react';
3
+ import { useState, useEffect } from 'react';
4
4
  import { useConsent } from './useConsent';
5
5
  const styles = {
6
6
  cookieBanner: {
@@ -41,7 +41,8 @@ const styles = {
41
41
  padding: '0.5rem 1rem',
42
42
  cursor: 'pointer',
43
43
  fontSize: '0.9rem',
44
- borderRadius: '4px'
44
+ borderRadius: '4px',
45
+ transition: 'background-color 0.2s ease'
45
46
  },
46
47
  cookieBannerText: {
47
48
  fontSize: '0.95rem',
@@ -53,32 +54,49 @@ const styles = {
53
54
  textDecoration: 'underline'
54
55
  }
55
56
  };
56
- const CookieBanner = ({ categories, content, storageStrategy }) => {
57
+ const CookieBanner = ({ categories, content, storageStrategy = 'localStorage' }) => {
57
58
  const categoryKeys = categories.map((c) => c.key);
58
- const { consent, isSet, saveConsent } = useConsent(categoryKeys, storageStrategy);
59
+ const { consent, isSet, saveConsent, isInitialized } = useConsent(categoryKeys, storageStrategy);
59
60
  const [showSettings, setShowSettings] = useState(false);
60
- const [tempConsent, setTempConsent] = useState(consent);
61
+ const [tempConsent, setTempConsent] = useState({});
62
+ // Update tempConsent whenever consent changes
63
+ useEffect(() => {
64
+ if (isInitialized) {
65
+ setTempConsent(consent);
66
+ }
67
+ }, [consent, isInitialized]);
68
+ // Don't render anything until initialized to prevent hydration mismatches
69
+ if (!isInitialized)
70
+ return null;
71
+ // Don't show banner if consent is already set
61
72
  if (isSet)
62
73
  return null;
63
74
  const updateTemp = (key, value) => {
64
75
  setTempConsent((prev) => (Object.assign(Object.assign({}, prev), { [key]: value })));
65
76
  };
66
77
  const acceptAll = () => {
67
- const allTrue = {};
78
+ const allTrue = { necessary: true };
68
79
  categoryKeys.forEach((key) => {
69
80
  allTrue[key] = true;
70
81
  });
71
- saveConsent(Object.assign({ necessary: true }, allTrue));
82
+ saveConsent(allTrue);
72
83
  };
73
84
  const saveSelected = () => {
74
85
  saveConsent(Object.assign({ necessary: true }, tempConsent));
75
86
  };
87
+ const acceptNecessaryOnly = () => {
88
+ const necessaryOnly = { necessary: true };
89
+ categoryKeys.forEach((key) => {
90
+ necessaryOnly[key] = false;
91
+ });
92
+ saveConsent(necessaryOnly);
93
+ };
76
94
  const handleButtonHover = (e) => {
77
95
  e.currentTarget.style.backgroundColor = '#333';
78
96
  };
79
97
  const handleButtonLeave = (e) => {
80
98
  e.currentTarget.style.backgroundColor = '#111';
81
99
  };
82
- return (_jsx("div", { style: styles.cookieBanner, className: "cookie-banner-container", children: !showSettings ? (_jsxs("div", { style: styles.cookieBannerBasic, className: "cookie-banner-default", children: [_jsx("div", { style: styles.cookieBannerText, children: content || (_jsxs("p", { children: ["We use cookies to improve your experience.", ' ', _jsx("a", { href: "/privacy-policy", target: "_blank", rel: "noopener noreferrer", style: styles.cookieBannerTextLink, children: "Learn more" }), "."] })) }), _jsxs("div", { style: styles.cookieBannerButtons, className: "cookie-banner-buttons", children: [_jsx("button", { className: "cookie-banner-button-necessary", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => saveConsent({ necessary: true }), children: "Only necessary" }), _jsx("button", { className: "cookie-banner-button-settings", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => setShowSettings(true), children: "Settings" }), _jsx("button", { className: "cookie-banner-button-all", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: acceptAll, children: "Accept all" })] })] })) : (_jsxs("div", { style: styles.cookieBannerSettings, className: "cookie-banner-settings", children: [_jsx("h4", { className: "cookie-banner-settings-heading", children: "Cookie Settings" }), _jsxs("label", { style: styles.cookieBannerSettingsLabel, className: "cookie-banner-category", children: [_jsx("input", { type: "checkbox", checked: true, disabled: true }), "Necessary (always active)"] }), categories.map((cat) => (_jsxs("label", { style: styles.cookieBannerSettingsLabel, className: "cookie-banner-category", children: [_jsx("input", { type: "checkbox", checked: tempConsent[cat.key] || false, onChange: (e) => updateTemp(cat.key, e.target.checked) }), cat.label] }, cat.key))), _jsxs("div", { style: styles.cookieBannerButtons, className: "cookie-banner-buttons", children: [_jsx("button", { className: "cookie-banner-button-save", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: saveSelected, children: "Save" }), _jsx("button", { className: "cookie-banner-button-back", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => setShowSettings(false), children: "Back" })] })] })) }));
100
+ return (_jsx("div", { style: styles.cookieBanner, className: "njs-cookie-banner-container", children: !showSettings ? (_jsxs("div", { style: styles.cookieBannerBasic, className: "njs-cookie-banner-default", children: [_jsx("div", { style: styles.cookieBannerText, children: content || (_jsxs("p", { children: ["We use cookies to improve your experience.", ' ', _jsx("a", { href: "/privacy-policy", target: "_blank", rel: "noopener noreferrer", style: styles.cookieBannerTextLink, children: "Learn more" }), "."] })) }), _jsxs("div", { style: styles.cookieBannerButtons, className: "njs-cookie-banner-buttons", children: [_jsx("button", { className: "njs-cookie-banner-button-necessary", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: acceptNecessaryOnly, children: "Only necessary" }), _jsx("button", { className: "njs-cookie-banner-button-settings", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => setShowSettings(true), children: "Settings" }), _jsx("button", { className: "njs-cookie-banner-button-all", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: acceptAll, children: "Accept all" })] })] })) : (_jsxs("div", { style: styles.cookieBannerSettings, className: "njs-cookie-banner-settings", children: [_jsx("h4", { className: "njs-cookie-banner-settings-heading", children: "Cookie Settings" }), _jsxs("label", { style: styles.cookieBannerSettingsLabel, className: "njs-cookie-banner-category", children: [_jsx("input", { type: "checkbox", checked: true, disabled: true }), "Necessary (always active)"] }), categories.map((cat) => (_jsxs("label", { style: styles.cookieBannerSettingsLabel, className: "njs-cookie-banner-category", children: [_jsx("input", { type: "checkbox", checked: tempConsent[cat.key] || false, onChange: (e) => updateTemp(cat.key, e.target.checked) }), cat.label] }, cat.key))), _jsxs("div", { style: styles.cookieBannerButtons, className: "njs-cookie-banner-buttons", children: [_jsx("button", { className: "njs-cookie-banner-button-save", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: saveSelected, children: "Save" }), _jsx("button", { className: "njs-cookie-banner-button-back", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => setShowSettings(false), children: "Back" })] })] })) }));
83
101
  };
84
102
  export default CookieBanner;
@@ -3,5 +3,6 @@ export declare function useConsent(categoryKeys: string[], storageStrategy?: Sto
3
3
  consent: ConsentState;
4
4
  isSet: boolean;
5
5
  saveConsent: (newConsent: ConsentState) => void;
6
+ isInitialized: boolean;
6
7
  };
7
8
  //# sourceMappingURL=useConsent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useConsent.d.ts","sourceRoot":"","sources":["../../src/useConsent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAExD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,eAAe,GAAE,eAAgC;;;8BA6C/D,YAAY;EAOhD"}
1
+ {"version":3,"file":"useConsent.d.ts","sourceRoot":"","sources":["../../src/useConsent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAExD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,eAAe,GAAE,eAAgC;;;8BAsEnD,YAAY;;EAc5D"}
@@ -1,12 +1,15 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, useCallback, useMemo } from 'react';
2
2
  export function useConsent(categoryKeys, storageStrategy = 'localStorage') {
3
3
  const storageKey = 'cookie-consent';
4
- const defaultConsent = Object.assign({ necessary: true }, categoryKeys.reduce((acc, key) => {
4
+ // Memoize defaultConsent to prevent unnecessary re-renders
5
+ const defaultConsent = useMemo(() => (Object.assign({ necessary: true }, categoryKeys.reduce((acc, key) => {
5
6
  acc[key] = false;
6
7
  return acc;
7
- }, {}));
8
- const getStoredConsent = () => {
8
+ }, {}))), [categoryKeys]);
9
+ const getStoredConsent = useCallback(() => {
9
10
  try {
11
+ if (typeof window === 'undefined')
12
+ return null; // SSR check
10
13
  if (storageStrategy === 'localStorage') {
11
14
  const stored = localStorage.getItem(storageKey);
12
15
  return stored ? JSON.parse(stored) : null;
@@ -16,32 +19,62 @@ export function useConsent(categoryKeys, storageStrategy = 'localStorage') {
16
19
  return match ? JSON.parse(decodeURIComponent(match[2])) : null;
17
20
  }
18
21
  }
19
- catch (_a) {
22
+ catch (error) {
23
+ console.warn('Error reading consent from storage:', error);
20
24
  return null;
21
25
  }
22
- };
23
- const storeConsent = (consent) => {
24
- const encoded = JSON.stringify(consent);
25
- if (storageStrategy === 'localStorage') {
26
- localStorage.setItem(storageKey, encoded);
26
+ }, [storageStrategy]);
27
+ const storeConsent = useCallback((consent) => {
28
+ try {
29
+ if (typeof window === 'undefined')
30
+ return; // SSR check
31
+ const encoded = JSON.stringify(consent);
32
+ if (storageStrategy === 'localStorage') {
33
+ localStorage.setItem(storageKey, encoded);
34
+ }
35
+ else {
36
+ const expires = new Date();
37
+ expires.setFullYear(expires.getFullYear() + 1);
38
+ document.cookie = `${storageKey}=${encodeURIComponent(encoded)}; path=/; SameSite=Lax; expires=${expires.toUTCString()}`;
39
+ }
27
40
  }
28
- else {
29
- document.cookie = `${storageKey}=${encodeURIComponent(encoded)}; path=/; SameSite=Lax`;
41
+ catch (error) {
42
+ console.error('Error storing consent:', error);
30
43
  }
31
- };
44
+ }, [storageStrategy]);
32
45
  const [consent, setConsent] = useState(defaultConsent);
33
46
  const [isSet, setIsSet] = useState(false);
47
+ const [isInitialized, setIsInitialized] = useState(false);
48
+ // Initialize consent from storage
34
49
  useEffect(() => {
50
+ if (typeof window === 'undefined')
51
+ return;
35
52
  const stored = getStoredConsent();
36
53
  if (stored) {
37
- setConsent(stored);
54
+ // Merge stored consent with default consent to ensure all categories are present
55
+ // This handles cases where new categories were added after the user already gave consent
56
+ const mergedConsent = Object.assign(Object.assign({}, defaultConsent), stored);
57
+ setConsent(mergedConsent);
38
58
  setIsSet(true);
39
59
  }
40
- }, []);
41
- const saveConsent = (newConsent) => {
42
- storeConsent(newConsent);
43
- setConsent(newConsent);
60
+ else {
61
+ // No stored consent found, use default
62
+ setConsent(defaultConsent);
63
+ setIsSet(false);
64
+ }
65
+ setIsInitialized(true);
66
+ }, [defaultConsent, getStoredConsent]);
67
+ const saveConsent = useCallback((newConsent) => {
68
+ const finalConsent = Object.assign({ necessary: true }, newConsent); // Ensure necessary is always true
69
+ storeConsent(finalConsent);
70
+ setConsent(finalConsent);
44
71
  setIsSet(true);
72
+ }, [storeConsent]);
73
+ // Return isInitialized to prevent rendering issues during SSR/hydration
74
+ return {
75
+ consent,
76
+ isSet: isSet && isInitialized,
77
+ saveConsent,
78
+ isInitialized
45
79
  };
46
- return { consent, isSet, saveConsent };
47
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextjs-cookie-consent",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "GDPR-compliant cookie consent banner with fully customizable categories for Next.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",