@tantainnovative/ndpr-toolkit 1.0.3 → 1.0.4
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/next-env.d.ts +5 -0
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -20
- package/.eslintrc.json +0 -10
- package/.github/workflows/ci.yml +0 -36
- package/.github/workflows/nextjs.yml +0 -104
- package/.husky/commit-msg +0 -4
- package/.husky/pre-commit +0 -4
- package/.lintstagedrc.js +0 -4
- package/.nvmrc +0 -1
- package/.versionrc +0 -17
- package/CLAUDE.md +0 -90
- package/commitlint.config.js +0 -36
- package/eslint.config.mjs +0 -16
- package/jest.config.js +0 -31
- package/jest.setup.js +0 -15
- package/next.config.js +0 -15
- package/next.config.ts +0 -62
- package/packages/ndpr-toolkit/README.md +0 -467
- package/packages/ndpr-toolkit/jest.config.js +0 -23
- package/packages/ndpr-toolkit/package-lock.json +0 -8197
- package/packages/ndpr-toolkit/package.json +0 -71
- package/packages/ndpr-toolkit/rollup.config.js +0 -34
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +0 -119
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +0 -122
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +0 -270
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +0 -199
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +0 -224
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +0 -104
- package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +0 -161
- package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +0 -330
- package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +0 -149
- package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +0 -88
- package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +0 -160
- package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +0 -110
- package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +0 -97
- package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +0 -701
- package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +0 -631
- package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +0 -569
- package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +0 -496
- package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +0 -270
- package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +0 -217
- package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +0 -206
- package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +0 -342
- package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +0 -373
- package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +0 -174
- package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +0 -717
- package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +0 -476
- package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +0 -620
- package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +0 -541
- package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +0 -454
- package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +0 -333
- package/packages/ndpr-toolkit/src/hooks/useBreach.ts +0 -409
- package/packages/ndpr-toolkit/src/hooks/useConsent.ts +0 -263
- package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +0 -457
- package/packages/ndpr-toolkit/src/hooks/useDSR.ts +0 -236
- package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +0 -428
- package/packages/ndpr-toolkit/src/index.ts +0 -44
- package/packages/ndpr-toolkit/src/setupTests.ts +0 -5
- package/packages/ndpr-toolkit/src/types/breach.ts +0 -283
- package/packages/ndpr-toolkit/src/types/consent.ts +0 -111
- package/packages/ndpr-toolkit/src/types/dpia.ts +0 -236
- package/packages/ndpr-toolkit/src/types/dsr.ts +0 -192
- package/packages/ndpr-toolkit/src/types/index.ts +0 -42
- package/packages/ndpr-toolkit/src/types/privacy.ts +0 -246
- package/packages/ndpr-toolkit/src/utils/breach.ts +0 -122
- package/packages/ndpr-toolkit/src/utils/consent.ts +0 -51
- package/packages/ndpr-toolkit/src/utils/dpia.ts +0 -104
- package/packages/ndpr-toolkit/src/utils/dsr.ts +0 -77
- package/packages/ndpr-toolkit/src/utils/privacy.ts +0 -100
- package/packages/ndpr-toolkit/tsconfig.json +0 -23
- package/postcss.config.mjs +0 -5
- package/src/__tests__/example.test.ts +0 -13
- package/src/__tests__/requestService.test.ts +0 -57
- package/src/app/accessibility.css +0 -70
- package/src/app/docs/components/DocLayout.tsx +0 -267
- package/src/app/docs/components/breach-notification/page.tsx +0 -797
- package/src/app/docs/components/consent-management/page.tsx +0 -576
- package/src/app/docs/components/data-subject-rights/page.tsx +0 -511
- package/src/app/docs/components/dpia-questionnaire/layout.tsx +0 -15
- package/src/app/docs/components/dpia-questionnaire/metadata.ts +0 -31
- package/src/app/docs/components/dpia-questionnaire/page.tsx +0 -666
- package/src/app/docs/components/hooks/page.tsx +0 -305
- package/src/app/docs/components/page.tsx +0 -84
- package/src/app/docs/components/privacy-policy-generator/page.tsx +0 -634
- package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +0 -123
- package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +0 -328
- package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +0 -28
- package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +0 -91
- package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +0 -118
- package/src/app/docs/guides/breach-notification-process/page.tsx +0 -39
- package/src/app/docs/guides/conducting-dpia/page.tsx +0 -593
- package/src/app/docs/guides/data-subject-requests/page.tsx +0 -666
- package/src/app/docs/guides/managing-consent/page.tsx +0 -738
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +0 -296
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +0 -145
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +0 -33
- package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +0 -99
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +0 -159
- package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +0 -38
- package/src/app/docs/guides/page.tsx +0 -67
- package/src/app/docs/layout.tsx +0 -15
- package/src/app/docs/metadata.ts +0 -31
- package/src/app/docs/page.tsx +0 -572
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +0 -123
- package/src/app/layout.tsx +0 -37
- package/src/app/ndpr-demos/breach/page.tsx +0 -354
- package/src/app/ndpr-demos/consent/page.tsx +0 -366
- package/src/app/ndpr-demos/dpia/page.tsx +0 -495
- package/src/app/ndpr-demos/dsr/page.tsx +0 -280
- package/src/app/ndpr-demos/page.tsx +0 -73
- package/src/app/ndpr-demos/policy/page.tsx +0 -771
- package/src/app/page.tsx +0 -452
- package/src/components/ErrorBoundary.tsx +0 -90
- package/src/components/breach-notification/BreachNotificationForm.tsx +0 -479
- package/src/components/consent/ConsentBanner.tsx +0 -159
- package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +0 -419
- package/src/components/docs/DocLayout.tsx +0 -289
- package/src/components/docs/index.ts +0 -2
- package/src/components/dpia/DPIAQuestionnaire.tsx +0 -483
- package/src/components/privacy-policy/PolicyGenerator.tsx +0 -1062
- package/src/components/privacy-policy/data.ts +0 -98
- package/src/components/privacy-policy/shared/CheckboxField.tsx +0 -38
- package/src/components/privacy-policy/shared/CheckboxGroup.tsx +0 -85
- package/src/components/privacy-policy/shared/FormField.tsx +0 -79
- package/src/components/privacy-policy/shared/StepIndicator.tsx +0 -86
- package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +0 -335
- package/src/components/privacy-policy/steps/DataCollectionStep.tsx +0 -231
- package/src/components/privacy-policy/steps/DataSharingStep.tsx +0 -418
- package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +0 -202
- package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +0 -172
- package/src/components/ui/Badge.tsx +0 -46
- package/src/components/ui/Button.tsx +0 -59
- package/src/components/ui/Card.tsx +0 -92
- package/src/components/ui/Checkbox.tsx +0 -57
- package/src/components/ui/FormField.tsx +0 -50
- package/src/components/ui/Input.tsx +0 -38
- package/src/components/ui/Loading.tsx +0 -201
- package/src/components/ui/Select.tsx +0 -42
- package/src/components/ui/TextArea.tsx +0 -38
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/switch.tsx +0 -31
- package/src/components/ui/tabs.tsx +0 -66
- package/src/hooks/useConsent.ts +0 -64
- package/src/hooks/useLoadingState.ts +0 -85
- package/src/lib/consentService.ts +0 -137
- package/src/lib/dpiaQuestions.ts +0 -148
- package/src/lib/requestService.ts +0 -75
- package/src/lib/sanitize.ts +0 -108
- package/src/lib/storage.ts +0 -222
- package/src/lib/utils.ts +0 -6
- package/src/types/html-to-docx.d.ts +0 -30
- package/src/types/index.ts +0 -72
- package/tailwind.config.ts +0 -65
- package/tsconfig.json +0 -41
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { ConsentOption, ConsentSettings } from '../../types/consent';
|
|
3
|
-
|
|
4
|
-
export interface ConsentBannerProps {
|
|
5
|
-
/**
|
|
6
|
-
* Array of consent options to display
|
|
7
|
-
*/
|
|
8
|
-
options: ConsentOption[];
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Callback function called when user saves their consent choices
|
|
12
|
-
*/
|
|
13
|
-
onSave: (settings: ConsentSettings) => void;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Title displayed on the banner
|
|
17
|
-
* @default "We Value Your Privacy"
|
|
18
|
-
*/
|
|
19
|
-
title?: string;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Description text displayed on the banner
|
|
23
|
-
* @default "We use cookies and similar technologies to provide our services and enhance your experience."
|
|
24
|
-
*/
|
|
25
|
-
description?: string;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Text for the accept all button
|
|
29
|
-
* @default "Accept All"
|
|
30
|
-
*/
|
|
31
|
-
acceptAllButtonText?: string;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Text for the reject all button
|
|
35
|
-
* @default "Reject All"
|
|
36
|
-
*/
|
|
37
|
-
rejectAllButtonText?: string;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Text for the customize button
|
|
41
|
-
* @default "Customize"
|
|
42
|
-
*/
|
|
43
|
-
customizeButtonText?: string;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Text for the save button
|
|
47
|
-
* @default "Save Preferences"
|
|
48
|
-
*/
|
|
49
|
-
saveButtonText?: string;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Position of the banner
|
|
53
|
-
* @default "bottom"
|
|
54
|
-
*/
|
|
55
|
-
position?: 'top' | 'bottom' | 'center';
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Version of the consent form
|
|
59
|
-
* @default "1.0"
|
|
60
|
-
*/
|
|
61
|
-
version?: string;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Whether to show the banner
|
|
65
|
-
* If not provided, the banner will be shown if no consent has been saved
|
|
66
|
-
*/
|
|
67
|
-
show?: boolean;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Storage key for consent settings
|
|
71
|
-
* @default "ndpr_consent"
|
|
72
|
-
*/
|
|
73
|
-
storageKey?: string;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Custom CSS class for the banner
|
|
77
|
-
*/
|
|
78
|
-
className?: string;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Custom CSS class for the buttons
|
|
82
|
-
*/
|
|
83
|
-
buttonClassName?: string;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Custom CSS class for the primary button
|
|
87
|
-
*/
|
|
88
|
-
primaryButtonClassName?: string;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Custom CSS class for the secondary button
|
|
92
|
-
*/
|
|
93
|
-
secondaryButtonClassName?: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export const ConsentBanner: React.FC<ConsentBannerProps> = ({
|
|
97
|
-
options,
|
|
98
|
-
onSave,
|
|
99
|
-
title = "We Value Your Privacy",
|
|
100
|
-
description = "We use cookies and similar technologies to provide our services and enhance your experience.",
|
|
101
|
-
acceptAllButtonText = "Accept All",
|
|
102
|
-
rejectAllButtonText = "Reject All",
|
|
103
|
-
customizeButtonText = "Customize",
|
|
104
|
-
saveButtonText = "Save Preferences",
|
|
105
|
-
position = "bottom",
|
|
106
|
-
version = "1.0",
|
|
107
|
-
show,
|
|
108
|
-
storageKey = "ndpr_consent",
|
|
109
|
-
className = "",
|
|
110
|
-
buttonClassName = "",
|
|
111
|
-
primaryButtonClassName = "",
|
|
112
|
-
secondaryButtonClassName = ""
|
|
113
|
-
}) => {
|
|
114
|
-
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
115
|
-
const [showCustomize, setShowCustomize] = useState<boolean>(false);
|
|
116
|
-
const [consents, setConsents] = useState<Record<string, boolean>>({});
|
|
117
|
-
|
|
118
|
-
// Initialize consents from options
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
const initialConsents: Record<string, boolean> = {};
|
|
121
|
-
options.forEach(option => {
|
|
122
|
-
initialConsents[option.id] = option.defaultValue || false;
|
|
123
|
-
});
|
|
124
|
-
setConsents(initialConsents);
|
|
125
|
-
|
|
126
|
-
// Check if consent is already saved
|
|
127
|
-
if (show === undefined) {
|
|
128
|
-
const savedConsent = localStorage.getItem(storageKey);
|
|
129
|
-
setIsOpen(!savedConsent);
|
|
130
|
-
} else {
|
|
131
|
-
setIsOpen(show);
|
|
132
|
-
}
|
|
133
|
-
}, [options, show, storageKey]);
|
|
134
|
-
|
|
135
|
-
const handleAcceptAll = () => {
|
|
136
|
-
const allConsents: Record<string, boolean> = {};
|
|
137
|
-
options.forEach(option => {
|
|
138
|
-
allConsents[option.id] = true;
|
|
139
|
-
});
|
|
140
|
-
saveConsent(allConsents);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const handleRejectAll = () => {
|
|
144
|
-
const rejectedConsents: Record<string, boolean> = {};
|
|
145
|
-
options.forEach(option => {
|
|
146
|
-
rejectedConsents[option.id] = option.required || false;
|
|
147
|
-
});
|
|
148
|
-
saveConsent(rejectedConsents);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const handleToggleConsent = (id: string, value: boolean) => {
|
|
152
|
-
setConsents(prev => ({
|
|
153
|
-
...prev,
|
|
154
|
-
[id]: value
|
|
155
|
-
}));
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const handleSavePreferences = () => {
|
|
159
|
-
saveConsent(consents);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const saveConsent = (consentValues: Record<string, boolean>) => {
|
|
163
|
-
const settings: ConsentSettings = {
|
|
164
|
-
consents: consentValues,
|
|
165
|
-
timestamp: Date.now(),
|
|
166
|
-
version,
|
|
167
|
-
method: showCustomize ? 'customize' : 'banner',
|
|
168
|
-
hasInteracted: true
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// Save to localStorage
|
|
172
|
-
localStorage.setItem(storageKey, JSON.stringify(settings));
|
|
173
|
-
|
|
174
|
-
// Call onSave callback
|
|
175
|
-
onSave(settings);
|
|
176
|
-
|
|
177
|
-
// Close the banner
|
|
178
|
-
setIsOpen(false);
|
|
179
|
-
setShowCustomize(false);
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
if (!isOpen) {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const positionClass =
|
|
187
|
-
position === 'top' ? 'top-0 left-0 right-0' :
|
|
188
|
-
position === 'center' ? 'top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 max-w-lg' :
|
|
189
|
-
'bottom-0 left-0 right-0';
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
<div
|
|
193
|
-
className={`fixed z-50 bg-white dark:bg-gray-800 shadow-lg p-4 border border-gray-200 dark:border-gray-700 ${positionClass} ${className}`}
|
|
194
|
-
role="dialog"
|
|
195
|
-
aria-labelledby="consent-banner-title"
|
|
196
|
-
>
|
|
197
|
-
<div className="max-w-6xl mx-auto">
|
|
198
|
-
<h2 id="consent-banner-title" className="text-lg font-bold mb-2">{title}</h2>
|
|
199
|
-
<p className="mb-4">{description}</p>
|
|
200
|
-
|
|
201
|
-
{showCustomize ? (
|
|
202
|
-
<div className="mb-4">
|
|
203
|
-
<div className="space-y-3">
|
|
204
|
-
{options.map(option => (
|
|
205
|
-
<div key={option.id} className="flex items-start">
|
|
206
|
-
<div className="flex items-center h-5">
|
|
207
|
-
<input
|
|
208
|
-
id={`consent-${option.id}`}
|
|
209
|
-
type="checkbox"
|
|
210
|
-
checked={consents[option.id] || false}
|
|
211
|
-
onChange={e => handleToggleConsent(option.id, e.target.checked)}
|
|
212
|
-
disabled={option.required}
|
|
213
|
-
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
214
|
-
/>
|
|
215
|
-
</div>
|
|
216
|
-
<div className="ml-3 text-sm">
|
|
217
|
-
<label htmlFor={`consent-${option.id}`} className="font-medium">
|
|
218
|
-
{option.label} {option.required && <span className="text-red-500">*</span>}
|
|
219
|
-
</label>
|
|
220
|
-
<p className="text-gray-500 dark:text-gray-400">{option.description}</p>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
))}
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
<div className="mt-4 flex flex-wrap gap-2">
|
|
227
|
-
<button
|
|
228
|
-
onClick={handleSavePreferences}
|
|
229
|
-
className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName} ${primaryButtonClassName}`}
|
|
230
|
-
>
|
|
231
|
-
{saveButtonText}
|
|
232
|
-
</button>
|
|
233
|
-
<button
|
|
234
|
-
onClick={() => setShowCustomize(false)}
|
|
235
|
-
className={`px-4 py-2 bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-white rounded hover:bg-gray-300 dark:hover:bg-gray-600 ${buttonClassName} ${secondaryButtonClassName}`}
|
|
236
|
-
>
|
|
237
|
-
Back
|
|
238
|
-
</button>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
) : (
|
|
242
|
-
<div className="flex flex-wrap gap-2">
|
|
243
|
-
<button
|
|
244
|
-
onClick={handleAcceptAll}
|
|
245
|
-
className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName} ${primaryButtonClassName}`}
|
|
246
|
-
>
|
|
247
|
-
{acceptAllButtonText}
|
|
248
|
-
</button>
|
|
249
|
-
<button
|
|
250
|
-
onClick={handleRejectAll}
|
|
251
|
-
className={`px-4 py-2 bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-white rounded hover:bg-gray-300 dark:hover:bg-gray-600 ${buttonClassName} ${secondaryButtonClassName}`}
|
|
252
|
-
>
|
|
253
|
-
{rejectAllButtonText}
|
|
254
|
-
</button>
|
|
255
|
-
<button
|
|
256
|
-
onClick={() => setShowCustomize(true)}
|
|
257
|
-
className={`px-4 py-2 bg-transparent text-gray-800 dark:text-white hover:underline ${buttonClassName}`}
|
|
258
|
-
>
|
|
259
|
-
{customizeButtonText}
|
|
260
|
-
</button>
|
|
261
|
-
</div>
|
|
262
|
-
)}
|
|
263
|
-
|
|
264
|
-
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
265
|
-
By clicking "Accept All", you agree to the use of ALL cookies. Visit our Cookie Policy to learn more.
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
</div>
|
|
269
|
-
);
|
|
270
|
-
};
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { ConsentOption, ConsentSettings } from '../../types/consent';
|
|
3
|
-
|
|
4
|
-
export interface ConsentManagerProps {
|
|
5
|
-
/**
|
|
6
|
-
* Array of consent options to display
|
|
7
|
-
*/
|
|
8
|
-
options: ConsentOption[];
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Current consent settings
|
|
12
|
-
*/
|
|
13
|
-
settings?: ConsentSettings;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Callback function called when user saves their consent choices
|
|
17
|
-
*/
|
|
18
|
-
onSave: (settings: ConsentSettings) => void;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Title displayed in the manager
|
|
22
|
-
* @default "Manage Your Privacy Settings"
|
|
23
|
-
*/
|
|
24
|
-
title?: string;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Description text displayed in the manager
|
|
28
|
-
* @default "Update your consent preferences at any time. Required cookies cannot be disabled as they are necessary for the website to function."
|
|
29
|
-
*/
|
|
30
|
-
description?: string;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Text for the save button
|
|
34
|
-
* @default "Save Preferences"
|
|
35
|
-
*/
|
|
36
|
-
saveButtonText?: string;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Text for the reset button
|
|
40
|
-
* @default "Reset to Defaults"
|
|
41
|
-
*/
|
|
42
|
-
resetButtonText?: string;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Version of the consent form
|
|
46
|
-
* @default "1.0"
|
|
47
|
-
*/
|
|
48
|
-
version?: string;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Custom CSS class for the manager
|
|
52
|
-
*/
|
|
53
|
-
className?: string;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Custom CSS class for the buttons
|
|
57
|
-
*/
|
|
58
|
-
buttonClassName?: string;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Custom CSS class for the primary button
|
|
62
|
-
*/
|
|
63
|
-
primaryButtonClassName?: string;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Custom CSS class for the secondary button
|
|
67
|
-
*/
|
|
68
|
-
secondaryButtonClassName?: string;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Whether to show a success message after saving
|
|
72
|
-
* @default true
|
|
73
|
-
*/
|
|
74
|
-
showSuccessMessage?: boolean;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Success message to display after saving
|
|
78
|
-
* @default "Your preferences have been saved."
|
|
79
|
-
*/
|
|
80
|
-
successMessage?: string;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Duration to show the success message (in milliseconds)
|
|
84
|
-
* @default 3000
|
|
85
|
-
*/
|
|
86
|
-
successMessageDuration?: number;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export const ConsentManager: React.FC<ConsentManagerProps> = ({
|
|
90
|
-
options,
|
|
91
|
-
settings,
|
|
92
|
-
onSave,
|
|
93
|
-
title = "Manage Your Privacy Settings",
|
|
94
|
-
description = "Update your consent preferences at any time. Required cookies cannot be disabled as they are necessary for the website to function.",
|
|
95
|
-
saveButtonText = "Save Preferences",
|
|
96
|
-
resetButtonText = "Reset to Defaults",
|
|
97
|
-
version = "1.0",
|
|
98
|
-
className = "",
|
|
99
|
-
buttonClassName = "",
|
|
100
|
-
primaryButtonClassName = "",
|
|
101
|
-
secondaryButtonClassName = "",
|
|
102
|
-
showSuccessMessage = true,
|
|
103
|
-
successMessage = "Your preferences have been saved.",
|
|
104
|
-
successMessageDuration = 3000
|
|
105
|
-
}) => {
|
|
106
|
-
const [consents, setConsents] = useState<Record<string, boolean>>({});
|
|
107
|
-
const [showSuccess, setShowSuccess] = useState<boolean>(false);
|
|
108
|
-
|
|
109
|
-
// Initialize consents from settings or options
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
if (settings && settings.consents) {
|
|
112
|
-
setConsents(settings.consents);
|
|
113
|
-
} else {
|
|
114
|
-
const initialConsents: Record<string, boolean> = {};
|
|
115
|
-
options.forEach(option => {
|
|
116
|
-
initialConsents[option.id] = option.defaultValue || false;
|
|
117
|
-
});
|
|
118
|
-
setConsents(initialConsents);
|
|
119
|
-
}
|
|
120
|
-
}, [options, settings]);
|
|
121
|
-
|
|
122
|
-
const handleToggleConsent = (id: string, value: boolean) => {
|
|
123
|
-
setConsents(prev => ({
|
|
124
|
-
...prev,
|
|
125
|
-
[id]: value
|
|
126
|
-
}));
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const handleSave = () => {
|
|
130
|
-
const newSettings: ConsentSettings = {
|
|
131
|
-
consents,
|
|
132
|
-
timestamp: Date.now(),
|
|
133
|
-
version,
|
|
134
|
-
method: 'manager',
|
|
135
|
-
hasInteracted: true
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
onSave(newSettings);
|
|
139
|
-
|
|
140
|
-
if (showSuccessMessage) {
|
|
141
|
-
setShowSuccess(true);
|
|
142
|
-
setTimeout(() => {
|
|
143
|
-
setShowSuccess(false);
|
|
144
|
-
}, successMessageDuration);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const handleReset = () => {
|
|
149
|
-
const defaultConsents: Record<string, boolean> = {};
|
|
150
|
-
options.forEach(option => {
|
|
151
|
-
defaultConsents[option.id] = option.defaultValue || false;
|
|
152
|
-
});
|
|
153
|
-
setConsents(defaultConsents);
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<div className={`bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 ${className}`}>
|
|
158
|
-
<h2 className="text-xl font-bold mb-2">{title}</h2>
|
|
159
|
-
<p className="mb-6 text-gray-600 dark:text-gray-300">{description}</p>
|
|
160
|
-
|
|
161
|
-
<div className="space-y-6">
|
|
162
|
-
{options.map(option => (
|
|
163
|
-
<div key={option.id} className="border-b border-gray-200 dark:border-gray-700 pb-4 last:border-0">
|
|
164
|
-
<div className="flex items-start justify-between">
|
|
165
|
-
<div>
|
|
166
|
-
<h3 className="font-medium text-gray-900 dark:text-white">{option.label}</h3>
|
|
167
|
-
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">{option.description}</p>
|
|
168
|
-
</div>
|
|
169
|
-
<div className="ml-4 flex-shrink-0">
|
|
170
|
-
<label className="relative inline-flex items-center cursor-pointer">
|
|
171
|
-
<input
|
|
172
|
-
type="checkbox"
|
|
173
|
-
className="sr-only peer"
|
|
174
|
-
checked={consents[option.id] || false}
|
|
175
|
-
onChange={e => handleToggleConsent(option.id, e.target.checked)}
|
|
176
|
-
disabled={option.required}
|
|
177
|
-
/>
|
|
178
|
-
<div className={`w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600 ${option.required ? 'opacity-60' : ''}`}></div>
|
|
179
|
-
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
|
|
180
|
-
{consents[option.id] ? 'Enabled' : 'Disabled'}
|
|
181
|
-
{option.required && <span className="text-xs text-red-500 ml-1">(Required)</span>}
|
|
182
|
-
</span>
|
|
183
|
-
</label>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
))}
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
{showSuccess && (
|
|
191
|
-
<div className="mt-4 p-3 bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200 rounded-md">
|
|
192
|
-
{successMessage}
|
|
193
|
-
</div>
|
|
194
|
-
)}
|
|
195
|
-
|
|
196
|
-
<div className="mt-6 flex flex-wrap gap-3">
|
|
197
|
-
<button
|
|
198
|
-
onClick={handleSave}
|
|
199
|
-
className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName} ${primaryButtonClassName}`}
|
|
200
|
-
>
|
|
201
|
-
{saveButtonText}
|
|
202
|
-
</button>
|
|
203
|
-
<button
|
|
204
|
-
onClick={handleReset}
|
|
205
|
-
className={`px-4 py-2 bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-white rounded hover:bg-gray-300 dark:hover:bg-gray-600 ${buttonClassName} ${secondaryButtonClassName}`}
|
|
206
|
-
>
|
|
207
|
-
{resetButtonText}
|
|
208
|
-
</button>
|
|
209
|
-
</div>
|
|
210
|
-
|
|
211
|
-
<div className="mt-4 text-xs text-gray-500 dark:text-gray-400">
|
|
212
|
-
<p>Last updated: {settings ? new Date(settings.timestamp).toLocaleString() : 'Never'}</p>
|
|
213
|
-
<p>Version: {version}</p>
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
);
|
|
217
|
-
};
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { ConsentSettings, ConsentStorageOptions } from '../../types/consent';
|
|
3
|
-
|
|
4
|
-
export interface ConsentStorageProps {
|
|
5
|
-
/**
|
|
6
|
-
* Current consent settings
|
|
7
|
-
*/
|
|
8
|
-
settings: ConsentSettings;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Storage options for consent settings
|
|
12
|
-
*/
|
|
13
|
-
storageOptions?: ConsentStorageOptions;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Callback function called when settings are loaded from storage
|
|
17
|
-
*/
|
|
18
|
-
onLoad?: (settings: ConsentSettings | null) => void;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Callback function called when settings are saved to storage
|
|
22
|
-
*/
|
|
23
|
-
onSave?: (settings: ConsentSettings) => void;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Whether to automatically save settings to storage
|
|
27
|
-
* @default true
|
|
28
|
-
*/
|
|
29
|
-
autoSave?: boolean;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Whether to automatically load settings from storage on mount
|
|
33
|
-
* @default true
|
|
34
|
-
*/
|
|
35
|
-
autoLoad?: boolean;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Children to render
|
|
39
|
-
* Can be either React nodes or a render prop function that receives storage methods
|
|
40
|
-
*/
|
|
41
|
-
children?: React.ReactNode | ((props: {
|
|
42
|
-
loadSettings: () => ConsentSettings | null;
|
|
43
|
-
saveSettings: (settings: ConsentSettings) => void;
|
|
44
|
-
clearSettings: () => void;
|
|
45
|
-
loaded: boolean;
|
|
46
|
-
}) => React.ReactNode);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const ConsentStorage = ({
|
|
50
|
-
settings,
|
|
51
|
-
storageOptions = {},
|
|
52
|
-
onLoad,
|
|
53
|
-
onSave,
|
|
54
|
-
autoSave = true,
|
|
55
|
-
autoLoad = true,
|
|
56
|
-
children
|
|
57
|
-
}: ConsentStorageProps): React.ReactElement | null => {
|
|
58
|
-
const {
|
|
59
|
-
storageKey = "ndpr_consent",
|
|
60
|
-
storageType = "localStorage",
|
|
61
|
-
cookieOptions = {}
|
|
62
|
-
} = storageOptions;
|
|
63
|
-
|
|
64
|
-
const [loaded, setLoaded] = useState<boolean>(false);
|
|
65
|
-
|
|
66
|
-
// Load consent settings from storage on mount
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (autoLoad && !loaded) {
|
|
69
|
-
loadSettings();
|
|
70
|
-
}
|
|
71
|
-
}, [autoLoad, loaded]);
|
|
72
|
-
|
|
73
|
-
// Save consent settings to storage when they change
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (autoSave && loaded) {
|
|
76
|
-
saveSettings(settings);
|
|
77
|
-
}
|
|
78
|
-
}, [settings, autoSave, loaded]);
|
|
79
|
-
|
|
80
|
-
// Load settings from storage
|
|
81
|
-
const loadSettings = (): ConsentSettings | null => {
|
|
82
|
-
let loadedSettings: ConsentSettings | null = null;
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
if (storageType === 'localStorage' && typeof window !== 'undefined') {
|
|
86
|
-
const savedData = localStorage.getItem(storageKey);
|
|
87
|
-
if (savedData) {
|
|
88
|
-
loadedSettings = JSON.parse(savedData);
|
|
89
|
-
}
|
|
90
|
-
} else if (storageType === 'sessionStorage' && typeof window !== 'undefined') {
|
|
91
|
-
const savedData = sessionStorage.getItem(storageKey);
|
|
92
|
-
if (savedData) {
|
|
93
|
-
loadedSettings = JSON.parse(savedData);
|
|
94
|
-
}
|
|
95
|
-
} else if (storageType === 'cookie' && typeof document !== 'undefined') {
|
|
96
|
-
const cookies = document.cookie.split(';');
|
|
97
|
-
const consentCookie = cookies.find(cookie => cookie.trim().startsWith(`${storageKey}=`));
|
|
98
|
-
if (consentCookie) {
|
|
99
|
-
const cookieValue = consentCookie.split('=')[1];
|
|
100
|
-
loadedSettings = JSON.parse(decodeURIComponent(cookieValue));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
setLoaded(true);
|
|
105
|
-
|
|
106
|
-
if (onLoad) {
|
|
107
|
-
onLoad(loadedSettings);
|
|
108
|
-
}
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error('Error loading consent settings:', error);
|
|
111
|
-
setLoaded(true);
|
|
112
|
-
|
|
113
|
-
if (onLoad) {
|
|
114
|
-
onLoad(null);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return loadedSettings;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Save settings to storage
|
|
122
|
-
const saveSettings = (settingsToSave: ConsentSettings): boolean => {
|
|
123
|
-
try {
|
|
124
|
-
const settingsString = JSON.stringify(settingsToSave);
|
|
125
|
-
|
|
126
|
-
if (storageType === 'localStorage' && typeof window !== 'undefined') {
|
|
127
|
-
localStorage.setItem(storageKey, settingsString);
|
|
128
|
-
} else if (storageType === 'sessionStorage' && typeof window !== 'undefined') {
|
|
129
|
-
sessionStorage.setItem(storageKey, settingsString);
|
|
130
|
-
} else if (storageType === 'cookie' && typeof document !== 'undefined') {
|
|
131
|
-
const {
|
|
132
|
-
domain,
|
|
133
|
-
path = '/',
|
|
134
|
-
expires = 365,
|
|
135
|
-
secure = true,
|
|
136
|
-
sameSite = 'Lax'
|
|
137
|
-
} = cookieOptions;
|
|
138
|
-
|
|
139
|
-
const expiryDate = new Date();
|
|
140
|
-
expiryDate.setDate(expiryDate.getDate() + expires);
|
|
141
|
-
|
|
142
|
-
let cookieString = `${storageKey}=${encodeURIComponent(settingsString)}; path=${path}; expires=${expiryDate.toUTCString()}`;
|
|
143
|
-
|
|
144
|
-
if (domain) {
|
|
145
|
-
cookieString += `; domain=${domain}`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (secure) {
|
|
149
|
-
cookieString += '; secure';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
cookieString += `; samesite=${sameSite}`;
|
|
153
|
-
|
|
154
|
-
document.cookie = cookieString;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (onSave) {
|
|
158
|
-
onSave(settingsToSave);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return true;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error('Error saving consent settings:', error);
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Clear settings from storage
|
|
169
|
-
const clearSettings = (): boolean => {
|
|
170
|
-
try {
|
|
171
|
-
if (storageType === 'localStorage' && typeof window !== 'undefined') {
|
|
172
|
-
localStorage.removeItem(storageKey);
|
|
173
|
-
} else if (storageType === 'sessionStorage' && typeof window !== 'undefined') {
|
|
174
|
-
sessionStorage.removeItem(storageKey);
|
|
175
|
-
} else if (storageType === 'cookie' && typeof document !== 'undefined') {
|
|
176
|
-
const { domain, path = '/' } = cookieOptions;
|
|
177
|
-
|
|
178
|
-
let cookieString = `${storageKey}=; path=${path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
179
|
-
|
|
180
|
-
if (domain) {
|
|
181
|
-
cookieString += `; domain=${domain}`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
document.cookie = cookieString;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return true;
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error('Error clearing consent settings:', error);
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// If children is a function, call it with the storage methods
|
|
195
|
-
if (typeof children === 'function') {
|
|
196
|
-
return <>{children({
|
|
197
|
-
loadSettings,
|
|
198
|
-
saveSettings,
|
|
199
|
-
clearSettings,
|
|
200
|
-
loaded
|
|
201
|
-
})}</>;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Otherwise, just render the children
|
|
205
|
-
return <>{children}</>;
|
|
206
|
-
};
|