nextjs-cookie-consent 2.0.0
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/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/components/CookieBanner.d.ts +3 -0
- package/dist/components/CookieBanner.js +98 -0
- package/dist/context/ConsentContext.d.ts +6 -0
- package/dist/context/ConsentContext.js +23 -0
- package/dist/hooks/useConsent.d.ts +1 -0
- package/dist/hooks/useConsent.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/styles/styles.css +137 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/storage.d.ts +5 -0
- package/dist/utils/storage.js +44 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 nextjs-cookie-consent
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# 🍪 nextjs-cookie-consent
|
|
2
|
+
|
|
3
|
+
A 🛡️ GDPR / DSGVO-compliant cookie consent banner for Next.js with fully customizable categories like analytics, marketing, or preferences. Built for modern apps with easy styling, flexible API, and localStorage- or cookie-based consent handling.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
✅ **GDPR / DSGVO-compliant**: Only essential cookies are enabled by default
|
|
8
|
+
🧠 **Fully configurable categories** (e.g. Necessary, Analytics, Marketing, ...)
|
|
9
|
+
💬 **Custom text** with links to your privacy policy
|
|
10
|
+
💾 **Consent stored** in localStorage (or cookies – configurable!)
|
|
11
|
+
🔄 **Dynamic access** to user consent via `useConsent()` hook
|
|
12
|
+
🧱 **Compatible** with both App Router and Pages Router
|
|
13
|
+
🎨 **Minimal styling** (fully customizable)
|
|
14
|
+
|
|
15
|
+
## 🚀 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install nextjs-cookie-consent
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
or with Yarn:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
yarn add nextjs-cookie-consent
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## ⚙️ Usage
|
|
28
|
+
|
|
29
|
+
### App Router (app/layout.tsx):
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { ConsentProvider, CookieBanner } from 'nextjs-cookie-consent';
|
|
33
|
+
import 'nextjs-cookie-consent/dist/styles/styles.css';
|
|
34
|
+
|
|
35
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
36
|
+
return (
|
|
37
|
+
<ConsentProvider>
|
|
38
|
+
<html lang="en">
|
|
39
|
+
<body>
|
|
40
|
+
{children}
|
|
41
|
+
<CookieBanner
|
|
42
|
+
categories={[
|
|
43
|
+
{ key: 'analytics', label: 'Analytics' },
|
|
44
|
+
{ key: 'marketing', label: 'Marketing' },
|
|
45
|
+
]}
|
|
46
|
+
storageStrategy="localStorage" // optional: "cookie" or "localStorage" (default)
|
|
47
|
+
content={
|
|
48
|
+
<p>
|
|
49
|
+
We use cookies to improve your experience.{' '}
|
|
50
|
+
<a href="/privacy-policy" target="_blank" rel="noopener noreferrer">
|
|
51
|
+
Learn more
|
|
52
|
+
</a>
|
|
53
|
+
.
|
|
54
|
+
</p>
|
|
55
|
+
}
|
|
56
|
+
/>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|
|
59
|
+
</ConsentProvider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Pages Router (_app.tsx):
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { ConsentProvider, CookieBanner } from 'nextjs-cookie-consent';
|
|
68
|
+
import 'nextjs-cookie-consent/dist/styles/styles.css';
|
|
69
|
+
import type { AppProps } from 'next/app';
|
|
70
|
+
|
|
71
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
72
|
+
return (
|
|
73
|
+
<ConsentProvider>
|
|
74
|
+
<Component {...pageProps} />
|
|
75
|
+
<CookieBanner
|
|
76
|
+
categories={[
|
|
77
|
+
{ key: 'analytics', label: 'Analytics' },
|
|
78
|
+
{ key: 'marketing', label: 'Marketing' },
|
|
79
|
+
]}
|
|
80
|
+
/>
|
|
81
|
+
</ConsentProvider>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 📊 Accessing Consent State
|
|
87
|
+
|
|
88
|
+
Use the `useConsent()` hook anywhere in your app:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
'use client';
|
|
92
|
+
|
|
93
|
+
import { useConsent } from 'nextjs-cookie-consent';
|
|
94
|
+
|
|
95
|
+
export default function AnalyticsLoader() {
|
|
96
|
+
const { consent } = useConsent(['analytics', 'marketing']);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
{consent.analytics && <p>📈 Analytics enabled</p>}
|
|
101
|
+
{!consent.analytics && <p>❌ Analytics disabled</p>}
|
|
102
|
+
</>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 🧪 Example: Load Google Analytics only after consent
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
'use client';
|
|
111
|
+
|
|
112
|
+
import Script from 'next/script';
|
|
113
|
+
import { useConsent } from 'nextjs-cookie-consent';
|
|
114
|
+
|
|
115
|
+
export default function GoogleAnalytics() {
|
|
116
|
+
const { consent } = useConsent(['analytics']);
|
|
117
|
+
|
|
118
|
+
if (!consent.analytics) return null;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX" strategy="afterInteractive" />
|
|
123
|
+
<Script id="ga-init" strategy="afterInteractive">
|
|
124
|
+
{`window.dataLayer = window.dataLayer || [];
|
|
125
|
+
function gtag(){dataLayer.push(arguments);}
|
|
126
|
+
gtag('js', new Date());
|
|
127
|
+
gtag('config', 'G-XXXXXXX');`}
|
|
128
|
+
</Script>
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 🧰 Props & API
|
|
135
|
+
|
|
136
|
+
| Prop | Type | Description |
|
|
137
|
+
|------|------|-------------|
|
|
138
|
+
| `categories` | `CookieCategory[]` | An array of consent categories to configure |
|
|
139
|
+
| `content` | `React.ReactNode` | (optional) Custom JSX/HTML content shown above the buttons |
|
|
140
|
+
| `storageStrategy` | `'localStorage' \| 'cookie'` | (optional) Where to store consent. Default: `'localStorage'` |
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
interface CookieCategory {
|
|
144
|
+
key: string; // e.g. "analytics"
|
|
145
|
+
label: string; // e.g. "Analytics"
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 🎨 Custom Styling
|
|
150
|
+
|
|
151
|
+
Import the CSS file and override the classes:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import 'nextjs-cookie-consent/dist/styles/styles.css';
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Available CSS classes:
|
|
158
|
+
- `.njs-cookie-banner-container` - Whole Banner
|
|
159
|
+
- `.njs-cookie-banner-default` - Default Banner view
|
|
160
|
+
- `.njs-cookie-banner-settings` - Settings Banner view
|
|
161
|
+
- `.njs-cookie-banner-buttons` - Button group
|
|
162
|
+
- `.njs-cookie-banner-button-necessary` - "Only necessary" button
|
|
163
|
+
- `.njs-cookie-banner-button-accept-all` - "Accept all" button
|
|
164
|
+
- `.njs-cookie-banner-button-settings` - "Settings" button
|
|
165
|
+
- And many more...
|
|
166
|
+
|
|
167
|
+
## ⚖️ License
|
|
168
|
+
|
|
169
|
+
MIT – free to use for personal and commercial projects.
|
|
170
|
+
|
|
171
|
+
Built with ❤️ for modern Next.js apps.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { useState, useEffect, useRef } from 'react';
|
|
14
|
+
import { useConsent } from '../context/ConsentContext';
|
|
15
|
+
import { storageUtils } from '../utils/storage';
|
|
16
|
+
export var CookieBanner = function (_a) {
|
|
17
|
+
var _b = _a.categories, categories = _b === void 0 ? [] : _b, content = _a.content, _c = _a.storageStrategy, storageStrategy = _c === void 0 ? 'localStorage' : _c;
|
|
18
|
+
var _d = useState(false), showBanner = _d[0], setShowBanner = _d[1];
|
|
19
|
+
var _e = useState(false), showSettings = _e[0], setShowSettings = _e[1];
|
|
20
|
+
var _f = useState({ necessary: true }), tempConsent = _f[0], setTempConsent = _f[1];
|
|
21
|
+
var _g = useConsent(), consent = _g.consent, updateConsent = _g.updateConsent;
|
|
22
|
+
var isInitialized = useRef(false);
|
|
23
|
+
// Initialize consent state only once
|
|
24
|
+
useEffect(function () {
|
|
25
|
+
if (isInitialized.current)
|
|
26
|
+
return;
|
|
27
|
+
var savedConsent = storageUtils.get(storageStrategy);
|
|
28
|
+
if (savedConsent) {
|
|
29
|
+
updateConsent(savedConsent);
|
|
30
|
+
setShowBanner(false);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Initialize with necessary only
|
|
34
|
+
var initialConsent_1 = { necessary: true };
|
|
35
|
+
categories.forEach(function (cat) {
|
|
36
|
+
initialConsent_1[cat.key] = false;
|
|
37
|
+
});
|
|
38
|
+
updateConsent(initialConsent_1);
|
|
39
|
+
setTempConsent(initialConsent_1);
|
|
40
|
+
setShowBanner(true);
|
|
41
|
+
}
|
|
42
|
+
isInitialized.current = true;
|
|
43
|
+
}, []); // Empty dependency array - run only once
|
|
44
|
+
// Separate effect to handle categories change (if needed)
|
|
45
|
+
useEffect(function () {
|
|
46
|
+
if (!isInitialized.current)
|
|
47
|
+
return;
|
|
48
|
+
// Update tempConsent if categories change after initialization
|
|
49
|
+
var newTempConsent = { necessary: true };
|
|
50
|
+
categories.forEach(function (cat) {
|
|
51
|
+
newTempConsent[cat.key] = consent[cat.key] || false;
|
|
52
|
+
});
|
|
53
|
+
setTempConsent(newTempConsent);
|
|
54
|
+
}, [categories, consent]);
|
|
55
|
+
var handleAcceptNecessary = function () {
|
|
56
|
+
var necessaryOnly = { necessary: true };
|
|
57
|
+
categories.forEach(function (cat) {
|
|
58
|
+
necessaryOnly[cat.key] = false;
|
|
59
|
+
});
|
|
60
|
+
updateConsent(necessaryOnly);
|
|
61
|
+
storageUtils.set(storageStrategy, necessaryOnly);
|
|
62
|
+
setShowBanner(false);
|
|
63
|
+
};
|
|
64
|
+
var handleAcceptAll = function () {
|
|
65
|
+
var acceptAll = { necessary: true };
|
|
66
|
+
categories.forEach(function (cat) {
|
|
67
|
+
acceptAll[cat.key] = true;
|
|
68
|
+
});
|
|
69
|
+
updateConsent(acceptAll);
|
|
70
|
+
storageUtils.set(storageStrategy, acceptAll);
|
|
71
|
+
setShowBanner(false);
|
|
72
|
+
};
|
|
73
|
+
var handleShowSettings = function () {
|
|
74
|
+
setTempConsent(__assign({}, consent));
|
|
75
|
+
setShowSettings(true);
|
|
76
|
+
};
|
|
77
|
+
var handleSaveSettings = function () {
|
|
78
|
+
updateConsent(tempConsent);
|
|
79
|
+
storageUtils.set(storageStrategy, tempConsent);
|
|
80
|
+
setShowSettings(false);
|
|
81
|
+
setShowBanner(false);
|
|
82
|
+
};
|
|
83
|
+
var handleBackToDefault = function () {
|
|
84
|
+
setShowSettings(false);
|
|
85
|
+
};
|
|
86
|
+
var handleCategoryToggle = function (categoryKey) {
|
|
87
|
+
if (categoryKey === 'necessary')
|
|
88
|
+
return; // Can't toggle necessary
|
|
89
|
+
setTempConsent(function (prev) {
|
|
90
|
+
var _a;
|
|
91
|
+
return (__assign(__assign({}, prev), (_a = {}, _a[categoryKey] = !prev[categoryKey], _a)));
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
if (!showBanner) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return (_jsx("div", { className: "njs-cookie-banner-container", children: !showSettings ? (_jsxs("div", { className: "njs-cookie-banner-default", children: [content && _jsx("div", { className: "njs-cookie-banner-content", children: content }), _jsxs("div", { className: "njs-cookie-banner-buttons", children: [_jsx("button", { className: "njs-cookie-banner-button-necessary", onClick: handleAcceptNecessary, children: "Nur notwendige" }), _jsx("button", { className: "njs-cookie-banner-button-accept-all", onClick: handleAcceptAll, children: "Alle akzeptieren" }), _jsx("button", { className: "njs-cookie-banner-button-settings", onClick: handleShowSettings, children: "Einstellungen" })] })] })) : (_jsxs("div", { className: "njs-cookie-banner-settings", children: [_jsx("h3", { className: "njs-cookie-banner-settings-heading", children: "Cookie-Settings" }), _jsxs("div", { className: "njs-cookie-banner-categories", children: [_jsxs("div", { className: "njs-cookie-banner-category", children: [_jsx("input", { type: "checkbox", id: "cookie-necessary", checked: true, disabled: true }), _jsx("label", { htmlFor: "cookie-necessary", children: "Necessary (always active)" })] }), categories.map(function (category) { return (_jsxs("div", { className: "njs-cookie-banner-category", children: [_jsx("input", { type: "checkbox", id: "cookie-".concat(category.key), checked: tempConsent[category.key] || false, onChange: function () { return handleCategoryToggle(category.key); } }), _jsx("label", { htmlFor: "cookie-".concat(category.key), children: category.label })] }, category.key)); })] }), _jsxs("div", { className: "njs-cookie-banner-buttons", children: [_jsx("button", { className: "njs-cookie-banner-button-back", onClick: handleBackToDefault, children: "Back" }), _jsx("button", { className: "njs-cookie-banner-button-save", onClick: handleSaveSettings, children: "Save" })] })] })) }));
|
|
98
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useState, useCallback } from 'react';
|
|
3
|
+
// Context
|
|
4
|
+
var ConsentContext = createContext(null);
|
|
5
|
+
// Context Provider Component
|
|
6
|
+
export var ConsentProvider = function (_a) {
|
|
7
|
+
var children = _a.children;
|
|
8
|
+
var _b = useState({ necessary: true }), consent = _b[0], setConsent = _b[1];
|
|
9
|
+
// Use useCallback to memoize the function and prevent unnecessary re-renders
|
|
10
|
+
var updateConsent = useCallback(function (newConsent) {
|
|
11
|
+
setConsent(newConsent);
|
|
12
|
+
}, []);
|
|
13
|
+
return (_jsx(ConsentContext.Provider, { value: { consent: consent, updateConsent: updateConsent }, children: children }));
|
|
14
|
+
};
|
|
15
|
+
// Hook
|
|
16
|
+
export var useConsent = function (requiredCategories) {
|
|
17
|
+
if (requiredCategories === void 0) { requiredCategories = []; }
|
|
18
|
+
var context = useContext(ConsentContext);
|
|
19
|
+
if (!context) {
|
|
20
|
+
throw new Error('useConsent must be used within a ConsentProvider');
|
|
21
|
+
}
|
|
22
|
+
return context;
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useConsent } from '../context/ConsentContext';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useConsent } from '../context/ConsentContext';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { CookieBanner } from './components/CookieBanner';
|
|
2
|
+
export { ConsentProvider, useConsent } from './context/ConsentContext';
|
|
3
|
+
export type { CookieCategory, ConsentState, CookieBannerProps, ConsentContextType } from './types';
|
|
4
|
+
export { storageUtils } from './utils/storage';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
.njs-cookie-banner-container {
|
|
2
|
+
position: fixed;
|
|
3
|
+
bottom: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
right: 0;
|
|
6
|
+
background: white;
|
|
7
|
+
border-top: 1px solid #e0e0e0;
|
|
8
|
+
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1);
|
|
9
|
+
padding: 20px;
|
|
10
|
+
z-index: 10000;
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.njs-cookie-banner-default,
|
|
15
|
+
.njs-cookie-banner-settings {
|
|
16
|
+
max-width: 1200px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.njs-cookie-banner-content {
|
|
21
|
+
margin-bottom: 15px;
|
|
22
|
+
color: #333;
|
|
23
|
+
line-height: 1.5;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.njs-cookie-banner-content a {
|
|
27
|
+
color: #0066cc;
|
|
28
|
+
text-decoration: underline;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.njs-cookie-banner-buttons {
|
|
32
|
+
display: flex;
|
|
33
|
+
gap: 12px;
|
|
34
|
+
flex-wrap: wrap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.njs-cookie-banner-button-necessary,
|
|
38
|
+
.njs-cookie-banner-button-settings,
|
|
39
|
+
.njs-cookie-banner-button-accept-all,
|
|
40
|
+
.njs-cookie-banner-button-save,
|
|
41
|
+
.njs-cookie-banner-button-back {
|
|
42
|
+
padding: 10px 20px;
|
|
43
|
+
border: 1px solid #ddd;
|
|
44
|
+
border-radius: 6px;
|
|
45
|
+
background: white;
|
|
46
|
+
color: #333;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
font-weight: 500;
|
|
50
|
+
transition: all 0.2s ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.njs-cookie-banner-button-necessary:hover,
|
|
54
|
+
.njs-cookie-banner-button-back:hover {
|
|
55
|
+
background: #f5f5f5;
|
|
56
|
+
border-color: #bbb;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.njs-cookie-banner-button-settings,
|
|
60
|
+
.njs-cookie-banner-button-accept-all,
|
|
61
|
+
.njs-cookie-banner-button-save {
|
|
62
|
+
background: #0066cc;
|
|
63
|
+
color: white;
|
|
64
|
+
border-color: #0066cc;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.njs-cookie-banner-button-settings:hover,
|
|
68
|
+
.njs-cookie-banner-button-accept-all:hover,
|
|
69
|
+
.njs-cookie-banner-button-save:hover {
|
|
70
|
+
background: #0052a3;
|
|
71
|
+
border-color: #0052a3;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.njs-cookie-banner-settings-heading {
|
|
75
|
+
margin: 0 0 20px 0;
|
|
76
|
+
font-size: 18px;
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
color: #333;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.njs-cookie-banner-categories {
|
|
82
|
+
margin-bottom: 20px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.njs-cookie-banner-category {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
margin-bottom: 12px;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
color: #333;
|
|
91
|
+
font-size: 14px;
|
|
92
|
+
position: relative;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.njs-cookie-banner-category input[type="checkbox"] {
|
|
96
|
+
margin-right: 8px;
|
|
97
|
+
width: 16px;
|
|
98
|
+
height: 16px;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
position: relative;
|
|
101
|
+
z-index: 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.njs-cookie-banner-category input[type="checkbox"]:disabled {
|
|
105
|
+
opacity: 0.6;
|
|
106
|
+
cursor: not-allowed;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.njs-cookie-banner-category label {
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
user-select: none;
|
|
112
|
+
margin-left: 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.njs-cookie-banner-category input[type="checkbox"]:disabled + label {
|
|
116
|
+
opacity: 0.6;
|
|
117
|
+
cursor: not-allowed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@media (max-width: 768px) {
|
|
121
|
+
.njs-cookie-banner-container {
|
|
122
|
+
padding: 15px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.njs-cookie-banner-buttons {
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.njs-cookie-banner-button-necessary,
|
|
130
|
+
.njs-cookie-banner-button-settings,
|
|
131
|
+
.njs-cookie-banner-button-accept-all,
|
|
132
|
+
.njs-cookie-banner-button-save,
|
|
133
|
+
.njs-cookie-banner-button-back {
|
|
134
|
+
width: 100%;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface CookieCategory {
|
|
2
|
+
key: string;
|
|
3
|
+
label: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ConsentState {
|
|
6
|
+
[key: string]: boolean;
|
|
7
|
+
necessary: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface CookieBannerProps {
|
|
10
|
+
categories?: CookieCategory[];
|
|
11
|
+
content?: React.ReactNode;
|
|
12
|
+
storageStrategy?: 'localStorage' | 'cookie';
|
|
13
|
+
}
|
|
14
|
+
export interface ConsentContextType {
|
|
15
|
+
consent: ConsentState;
|
|
16
|
+
updateConsent: (newConsent: ConsentState) => void;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
var CONSENT_KEY = 'nextjs-cookie-consent';
|
|
2
|
+
export var storageUtils = {
|
|
3
|
+
get: function (strategy) {
|
|
4
|
+
if (typeof window === 'undefined')
|
|
5
|
+
return null;
|
|
6
|
+
try {
|
|
7
|
+
if (strategy === 'localStorage') {
|
|
8
|
+
var stored = localStorage.getItem(CONSENT_KEY);
|
|
9
|
+
return stored ? JSON.parse(stored) : null;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
var cookies = document.cookie.split(';');
|
|
13
|
+
var consentCookie = cookies.find(function (c) { return c.trim().startsWith("".concat(CONSENT_KEY, "=")); });
|
|
14
|
+
if (consentCookie) {
|
|
15
|
+
var value = consentCookie.split('=')[1];
|
|
16
|
+
return JSON.parse(decodeURIComponent(value));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error('Error reading consent:', error);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
},
|
|
25
|
+
set: function (strategy, consent) {
|
|
26
|
+
if (typeof window === 'undefined')
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
var value = JSON.stringify(consent);
|
|
30
|
+
if (strategy === 'localStorage') {
|
|
31
|
+
localStorage.setItem(CONSENT_KEY, value);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Set cookie with 1 year expiry
|
|
35
|
+
var expires = new Date();
|
|
36
|
+
expires.setFullYear(expires.getFullYear() + 1);
|
|
37
|
+
document.cookie = "".concat(CONSENT_KEY, "=").concat(encodeURIComponent(value), "; expires=").concat(expires.toUTCString(), "; path=/; SameSite=Lax");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error('Error saving consent:', error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nextjs-cookie-consent",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "A GDPR/DSGVO-compliant cookie consent banner for Next.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc && cp -r src/styles dist/",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"clean": "rm -rf dist",
|
|
16
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
17
|
+
"test": "echo \"No tests yet\"",
|
|
18
|
+
"lint": "echo \"No linting yet\""
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"nextjs",
|
|
22
|
+
"cookie",
|
|
23
|
+
"consent",
|
|
24
|
+
"gdpr",
|
|
25
|
+
"dsgvo",
|
|
26
|
+
"privacy",
|
|
27
|
+
"react",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
30
|
+
"author": "Your Name <your.email@example.com>",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": ">=16.8.0",
|
|
34
|
+
"react-dom": ">=16.8.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/react": "^18.0.0",
|
|
38
|
+
"@types/react-dom": "^18.0.0",
|
|
39
|
+
"typescript": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/yourusername/nextjs-cookie-consent.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/yourusername/nextjs-cookie-consent/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/yourusername/nextjs-cookie-consent#readme"
|
|
49
|
+
}
|