@tantainnovative/ndpr-toolkit 1.0.2 → 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.
- package/.claude/settings.local.json +20 -0
- package/.eslintrc.json +10 -0
- package/.github/workflows/ci.yml +36 -0
- package/.github/workflows/nextjs.yml +104 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +4 -0
- package/.lintstagedrc.js +4 -0
- package/.nvmrc +1 -0
- package/.versionrc +17 -0
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +90 -0
- package/CNAME +1 -0
- package/CONTRIBUTING.md +87 -0
- package/README.md +84 -447
- package/RELEASE-NOTES-v1.0.0.md +140 -0
- package/RELEASE-NOTES-v1.0.1.md +69 -0
- package/SECURITY.md +21 -0
- package/commitlint.config.js +36 -0
- package/components.json +21 -0
- package/eslint.config.mjs +16 -0
- package/jest.config.js +31 -0
- package/jest.setup.js +15 -0
- package/next.config.js +15 -0
- package/next.config.ts +62 -0
- package/package.json +70 -52
- package/packages/ndpr-toolkit/README.md +467 -0
- package/packages/ndpr-toolkit/jest.config.js +23 -0
- package/packages/ndpr-toolkit/package-lock.json +8197 -0
- package/packages/ndpr-toolkit/package.json +71 -0
- package/packages/ndpr-toolkit/rollup.config.js +34 -0
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +119 -0
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +122 -0
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +270 -0
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +199 -0
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +224 -0
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +104 -0
- package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +161 -0
- package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +330 -0
- package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +149 -0
- package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +88 -0
- package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +160 -0
- package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +110 -0
- package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +97 -0
- package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +701 -0
- package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +631 -0
- package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +569 -0
- package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +496 -0
- package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +270 -0
- package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +217 -0
- package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +206 -0
- package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +342 -0
- package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +373 -0
- package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +174 -0
- package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +717 -0
- package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +476 -0
- package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +620 -0
- package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +541 -0
- package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +454 -0
- package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +333 -0
- package/packages/ndpr-toolkit/src/hooks/useBreach.ts +409 -0
- package/packages/ndpr-toolkit/src/hooks/useConsent.ts +263 -0
- package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +457 -0
- package/packages/ndpr-toolkit/src/hooks/useDSR.ts +236 -0
- package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +428 -0
- package/{dist/index.d.ts → packages/ndpr-toolkit/src/index.ts} +13 -0
- package/packages/ndpr-toolkit/src/setupTests.ts +5 -0
- package/packages/ndpr-toolkit/src/types/breach.ts +283 -0
- package/packages/ndpr-toolkit/src/types/consent.ts +111 -0
- package/packages/ndpr-toolkit/src/types/dpia.ts +236 -0
- package/packages/ndpr-toolkit/src/types/dsr.ts +192 -0
- package/packages/ndpr-toolkit/src/types/index.ts +42 -0
- package/packages/ndpr-toolkit/src/types/privacy.ts +246 -0
- package/packages/ndpr-toolkit/src/utils/breach.ts +122 -0
- package/packages/ndpr-toolkit/src/utils/consent.ts +51 -0
- package/packages/ndpr-toolkit/src/utils/dpia.ts +104 -0
- package/packages/ndpr-toolkit/src/utils/dsr.ts +77 -0
- package/packages/ndpr-toolkit/src/utils/privacy.ts +100 -0
- package/packages/ndpr-toolkit/tsconfig.json +23 -0
- package/postcss.config.mjs +5 -0
- package/public/NDPR TOOLKIT.svg +1 -0
- package/public/favicon/android-chrome-192x192.png +0 -0
- package/public/favicon/android-chrome-512x512.png +0 -0
- package/public/favicon/apple-touch-icon.png +0 -0
- package/public/favicon/favicon-16x16.png +0 -0
- package/public/favicon/favicon-32x32.png +0 -0
- package/public/favicon/site.webmanifest +1 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/ndpr-toolkit-logo.svg +108 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/__tests__/example.test.ts +13 -0
- package/src/__tests__/requestService.test.ts +57 -0
- package/src/app/accessibility.css +70 -0
- package/src/app/docs/components/DocLayout.tsx +267 -0
- package/src/app/docs/components/breach-notification/page.tsx +797 -0
- package/src/app/docs/components/consent-management/page.tsx +576 -0
- package/src/app/docs/components/data-subject-rights/page.tsx +511 -0
- package/src/app/docs/components/dpia-questionnaire/layout.tsx +15 -0
- package/src/app/docs/components/dpia-questionnaire/metadata.ts +31 -0
- package/src/app/docs/components/dpia-questionnaire/page.tsx +666 -0
- package/src/app/docs/components/hooks/page.tsx +305 -0
- package/src/app/docs/components/page.tsx +84 -0
- package/src/app/docs/components/privacy-policy-generator/page.tsx +634 -0
- package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +123 -0
- package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +328 -0
- package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +28 -0
- package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +91 -0
- package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +118 -0
- package/src/app/docs/guides/breach-notification-process/page.tsx +39 -0
- package/src/app/docs/guides/conducting-dpia/page.tsx +593 -0
- package/src/app/docs/guides/data-subject-requests/page.tsx +666 -0
- package/src/app/docs/guides/managing-consent/page.tsx +738 -0
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +296 -0
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +145 -0
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +33 -0
- package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +99 -0
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +159 -0
- package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +38 -0
- package/src/app/docs/guides/page.tsx +67 -0
- package/src/app/docs/layout.tsx +15 -0
- package/src/app/docs/metadata.ts +31 -0
- package/src/app/docs/page.tsx +572 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +123 -0
- package/src/app/layout.tsx +37 -0
- package/src/app/ndpr-demos/breach/page.tsx +354 -0
- package/src/app/ndpr-demos/consent/page.tsx +366 -0
- package/src/app/ndpr-demos/dpia/page.tsx +495 -0
- package/src/app/ndpr-demos/dsr/page.tsx +280 -0
- package/src/app/ndpr-demos/page.tsx +73 -0
- package/src/app/ndpr-demos/policy/page.tsx +771 -0
- package/src/app/page.tsx +452 -0
- package/src/components/ErrorBoundary.tsx +90 -0
- package/src/components/breach-notification/BreachNotificationForm.tsx +479 -0
- package/src/components/consent/ConsentBanner.tsx +159 -0
- package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +419 -0
- package/src/components/docs/DocLayout.tsx +289 -0
- package/src/components/docs/index.ts +2 -0
- package/src/components/dpia/DPIAQuestionnaire.tsx +483 -0
- package/src/components/privacy-policy/PolicyGenerator.tsx +1062 -0
- package/src/components/privacy-policy/data.ts +98 -0
- package/src/components/privacy-policy/shared/CheckboxField.tsx +38 -0
- package/src/components/privacy-policy/shared/CheckboxGroup.tsx +85 -0
- package/src/components/privacy-policy/shared/FormField.tsx +79 -0
- package/src/components/privacy-policy/shared/StepIndicator.tsx +86 -0
- package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +335 -0
- package/src/components/privacy-policy/steps/DataCollectionStep.tsx +231 -0
- package/src/components/privacy-policy/steps/DataSharingStep.tsx +418 -0
- package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +202 -0
- package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +172 -0
- package/src/components/ui/Badge.tsx +46 -0
- package/src/components/ui/Button.tsx +59 -0
- package/src/components/ui/Card.tsx +92 -0
- package/src/components/ui/Checkbox.tsx +57 -0
- package/src/components/ui/FormField.tsx +50 -0
- package/src/components/ui/Input.tsx +38 -0
- package/src/components/ui/Loading.tsx +201 -0
- package/src/components/ui/Select.tsx +42 -0
- package/src/components/ui/TextArea.tsx +38 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/tabs.tsx +66 -0
- package/src/hooks/useConsent.ts +64 -0
- package/src/hooks/useLoadingState.ts +85 -0
- package/src/lib/consentService.ts +137 -0
- package/src/lib/dpiaQuestions.ts +148 -0
- package/src/lib/requestService.ts +75 -0
- package/src/lib/sanitize.ts +108 -0
- package/src/lib/storage.ts +222 -0
- package/src/lib/utils.ts +6 -0
- package/src/types/html-to-docx.d.ts +30 -0
- package/src/types/index.ts +72 -0
- package/tailwind.config.ts +65 -0
- package/tsconfig.json +41 -0
- package/dist/components/breach/BreachNotificationManager.d.ts +0 -62
- package/dist/components/breach/BreachReportForm.d.ts +0 -66
- package/dist/components/breach/BreachRiskAssessment.d.ts +0 -50
- package/dist/components/breach/RegulatoryReportGenerator.d.ts +0 -94
- package/dist/components/consent/ConsentBanner.d.ts +0 -79
- package/dist/components/consent/ConsentManager.d.ts +0 -73
- package/dist/components/consent/ConsentStorage.d.ts +0 -41
- package/dist/components/dpia/DPIAQuestionnaire.d.ts +0 -70
- package/dist/components/dpia/DPIAReport.d.ts +0 -40
- package/dist/components/dpia/StepIndicator.d.ts +0 -64
- package/dist/components/dsr/DSRDashboard.d.ts +0 -58
- package/dist/components/dsr/DSRRequestForm.d.ts +0 -74
- package/dist/components/dsr/DSRTracker.d.ts +0 -56
- package/dist/components/policy/PolicyExporter.d.ts +0 -65
- package/dist/components/policy/PolicyGenerator.d.ts +0 -54
- package/dist/components/policy/PolicyPreview.d.ts +0 -71
- package/dist/hooks/useBreach.d.ts +0 -97
- package/dist/hooks/useConsent.d.ts +0 -63
- package/dist/hooks/useDPIA.d.ts +0 -92
- package/dist/hooks/useDSR.d.ts +0 -72
- package/dist/hooks/usePrivacyPolicy.d.ts +0 -87
- package/dist/index.esm.js +0 -2
- package/dist/index.esm.js.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/setupTests.d.ts +0 -2
- package/dist/types/breach.d.ts +0 -239
- package/dist/types/consent.d.ts +0 -95
- package/dist/types/dpia.d.ts +0 -196
- package/dist/types/dsr.d.ts +0 -162
- package/dist/types/privacy.d.ts +0 -204
- package/dist/utils/breach.d.ts +0 -14
- package/dist/utils/consent.d.ts +0 -10
- package/dist/utils/dpia.d.ts +0 -12
- package/dist/utils/dsr.d.ts +0 -11
- package/dist/utils/privacy.d.ts +0 -12
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { generatePolicyText } from '../../utils/privacy';
|
|
3
|
+
import { PolicySection, PolicyVariable } from '../../types/privacy';
|
|
4
|
+
|
|
5
|
+
// Using PolicySection and PolicyVariable from types/privacy.ts
|
|
6
|
+
|
|
7
|
+
export interface PolicyGeneratorProps {
|
|
8
|
+
/**
|
|
9
|
+
* List of policy sections
|
|
10
|
+
*/
|
|
11
|
+
sections: PolicySection[];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List of policy variables
|
|
15
|
+
*/
|
|
16
|
+
variables: PolicyVariable[];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Callback function called when the policy is generated
|
|
20
|
+
*/
|
|
21
|
+
onGenerate: (policy: { sections: PolicySection[], variables: PolicyVariable[], content: string }) => void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Title displayed on the generator
|
|
25
|
+
* @default "NDPR Privacy Policy Generator"
|
|
26
|
+
*/
|
|
27
|
+
title?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Description text displayed on the generator
|
|
31
|
+
* @default "Generate an NDPR-compliant privacy policy for your organization."
|
|
32
|
+
*/
|
|
33
|
+
description?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Custom CSS class for the generator
|
|
37
|
+
*/
|
|
38
|
+
className?: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Custom CSS class for the buttons
|
|
42
|
+
*/
|
|
43
|
+
buttonClassName?: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Text for the generate button
|
|
47
|
+
* @default "Generate Policy"
|
|
48
|
+
*/
|
|
49
|
+
generateButtonText?: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether to show a preview of the generated policy
|
|
53
|
+
* @default true
|
|
54
|
+
*/
|
|
55
|
+
showPreview?: boolean;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether to allow editing the policy content
|
|
59
|
+
* @default true
|
|
60
|
+
*/
|
|
61
|
+
allowEditing?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const PolicyGenerator: React.FC<PolicyGeneratorProps> = ({
|
|
65
|
+
sections: initialSections,
|
|
66
|
+
variables: initialVariables,
|
|
67
|
+
onGenerate,
|
|
68
|
+
title = "NDPR Privacy Policy Generator",
|
|
69
|
+
description = "Generate an NDPR-compliant privacy policy for your organization.",
|
|
70
|
+
className = "",
|
|
71
|
+
buttonClassName = "",
|
|
72
|
+
generateButtonText = "Generate Policy",
|
|
73
|
+
showPreview = true,
|
|
74
|
+
allowEditing = true
|
|
75
|
+
}) => {
|
|
76
|
+
const [sections, setSections] = useState<PolicySection[]>(initialSections);
|
|
77
|
+
const [variables, setVariables] = useState<PolicyVariable[]>(initialVariables);
|
|
78
|
+
const [activeStep, setActiveStep] = useState<'sections' | 'variables' | 'preview'>('sections');
|
|
79
|
+
const [generatedPolicy, setGeneratedPolicy] = useState<string>('');
|
|
80
|
+
const [editedPolicy, setEditedPolicy] = useState<string>('');
|
|
81
|
+
const [isGenerated, setIsGenerated] = useState<boolean>(false);
|
|
82
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
83
|
+
|
|
84
|
+
// Update sections when initialSections changes
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
setSections(initialSections);
|
|
87
|
+
}, [initialSections]);
|
|
88
|
+
|
|
89
|
+
// Update variables when initialVariables changes
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
setVariables(initialVariables);
|
|
92
|
+
}, [initialVariables]);
|
|
93
|
+
|
|
94
|
+
// Handle section toggle
|
|
95
|
+
const handleSectionToggle = (sectionId: string) => {
|
|
96
|
+
setSections(prevSections =>
|
|
97
|
+
prevSections.map(section =>
|
|
98
|
+
section.id === sectionId
|
|
99
|
+
? { ...section, included: !section.included }
|
|
100
|
+
: section
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Handle variable change
|
|
106
|
+
const handleVariableChange = (variableId: string, value: string) => {
|
|
107
|
+
setVariables(prevVariables =>
|
|
108
|
+
prevVariables.map(variable =>
|
|
109
|
+
variable.id === variableId
|
|
110
|
+
? { ...variable, value }
|
|
111
|
+
: variable
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Clear error for this variable if it exists
|
|
116
|
+
if (errors[variableId]) {
|
|
117
|
+
setErrors(prevErrors => {
|
|
118
|
+
const newErrors = { ...prevErrors };
|
|
119
|
+
delete newErrors[variableId];
|
|
120
|
+
return newErrors;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Validate variables
|
|
126
|
+
const validateVariables = (): boolean => {
|
|
127
|
+
const newErrors: Record<string, string> = {};
|
|
128
|
+
let isValid = true;
|
|
129
|
+
|
|
130
|
+
variables.forEach(variable => {
|
|
131
|
+
if (variable.required && !variable.value) {
|
|
132
|
+
newErrors[variable.id] = `${variable.name} is required`;
|
|
133
|
+
isValid = false;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
setErrors(newErrors);
|
|
138
|
+
return isValid;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Generate policy
|
|
142
|
+
const generatePolicy = () => {
|
|
143
|
+
if (!validateVariables()) {
|
|
144
|
+
setActiveStep('variables');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const includedSections = sections.filter(section => section.included);
|
|
149
|
+
const variableMap = Object.fromEntries(
|
|
150
|
+
variables.map(variable => [variable.name, variable.value])
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
let policyContent = '';
|
|
154
|
+
|
|
155
|
+
includedSections.forEach(section => {
|
|
156
|
+
policyContent += `## ${section.title}\n\n`;
|
|
157
|
+
policyContent += generatePolicyText(section.template, variableMap);
|
|
158
|
+
policyContent += '\n\n';
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
setGeneratedPolicy(policyContent);
|
|
162
|
+
setEditedPolicy(policyContent);
|
|
163
|
+
setIsGenerated(true);
|
|
164
|
+
setActiveStep('preview');
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Handle policy submission
|
|
168
|
+
const handleSubmit = () => {
|
|
169
|
+
const policy = {
|
|
170
|
+
sections,
|
|
171
|
+
variables,
|
|
172
|
+
content: allowEditing ? editedPolicy : generatedPolicy
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
onGenerate(policy);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Render section list
|
|
179
|
+
const renderSectionList = () => {
|
|
180
|
+
return (
|
|
181
|
+
<div className="space-y-4">
|
|
182
|
+
<h3 className="text-lg font-medium mb-4">Select Policy Sections</h3>
|
|
183
|
+
|
|
184
|
+
{sections.map(section => (
|
|
185
|
+
<div key={section.id} className="border border-gray-200 dark:border-gray-700 rounded-md p-4">
|
|
186
|
+
<div className="flex items-start">
|
|
187
|
+
<div className="flex items-center h-5">
|
|
188
|
+
<input
|
|
189
|
+
id={`section-${section.id}`}
|
|
190
|
+
type="checkbox"
|
|
191
|
+
checked={section.included}
|
|
192
|
+
onChange={() => handleSectionToggle(section.id)}
|
|
193
|
+
disabled={section.required}
|
|
194
|
+
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="ml-3 text-sm">
|
|
198
|
+
<label htmlFor={`section-${section.id}`} className="font-medium text-gray-900 dark:text-white">
|
|
199
|
+
{section.title} {section.required && <span className="text-red-500">*</span>}
|
|
200
|
+
</label>
|
|
201
|
+
{section.description && (
|
|
202
|
+
<p className="text-gray-500 dark:text-gray-400 mt-1">{section.description}</p>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
))}
|
|
208
|
+
|
|
209
|
+
<div className="mt-6">
|
|
210
|
+
<button
|
|
211
|
+
onClick={() => setActiveStep('variables')}
|
|
212
|
+
className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName}`}
|
|
213
|
+
>
|
|
214
|
+
Next: Fill Variables
|
|
215
|
+
</button>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Render variable form
|
|
222
|
+
const renderVariableForm = () => {
|
|
223
|
+
// Group variables by section
|
|
224
|
+
const variablesBySection: Record<string, PolicyVariable[]> = {};
|
|
225
|
+
|
|
226
|
+
variables.forEach(variable => {
|
|
227
|
+
const sectionId = variable.id.split('.')[0];
|
|
228
|
+
if (!variablesBySection[sectionId]) {
|
|
229
|
+
variablesBySection[sectionId] = [];
|
|
230
|
+
}
|
|
231
|
+
variablesBySection[sectionId].push(variable);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div>
|
|
236
|
+
<h3 className="text-lg font-medium mb-4">Fill Policy Variables</h3>
|
|
237
|
+
|
|
238
|
+
<div className="space-y-6">
|
|
239
|
+
{Object.entries(variablesBySection).map(([sectionId, sectionVariables]) => {
|
|
240
|
+
const section = sections.find(s => s.id === sectionId);
|
|
241
|
+
|
|
242
|
+
// Skip sections that are not included
|
|
243
|
+
if (section && !section.included) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<div key={sectionId} className="border border-gray-200 dark:border-gray-700 rounded-md p-4">
|
|
249
|
+
<h4 className="font-medium text-lg mb-3">
|
|
250
|
+
{section ? section.title : 'General Information'}
|
|
251
|
+
</h4>
|
|
252
|
+
|
|
253
|
+
<div className="space-y-4">
|
|
254
|
+
{sectionVariables.map(variable => (
|
|
255
|
+
<div key={variable.id}>
|
|
256
|
+
<label htmlFor={`var-${variable.id}`} className="block text-sm font-medium mb-1">
|
|
257
|
+
{variable.name} {variable.required && <span className="text-red-500">*</span>}
|
|
258
|
+
</label>
|
|
259
|
+
|
|
260
|
+
{variable.description && (
|
|
261
|
+
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
262
|
+
{variable.description}
|
|
263
|
+
</p>
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
{variable.inputType === 'textarea' ? (
|
|
267
|
+
<textarea
|
|
268
|
+
id={`var-${variable.id}`}
|
|
269
|
+
value={variable.value}
|
|
270
|
+
onChange={e => handleVariableChange(variable.id, e.target.value)}
|
|
271
|
+
rows={4}
|
|
272
|
+
className={`w-full px-3 py-2 border ${
|
|
273
|
+
errors[variable.id]
|
|
274
|
+
? 'border-red-500 focus:ring-red-500'
|
|
275
|
+
: 'border-gray-300 dark:border-gray-600 focus:ring-blue-500'
|
|
276
|
+
} rounded-md focus:outline-none focus:ring-2`}
|
|
277
|
+
required={variable.required}
|
|
278
|
+
/>
|
|
279
|
+
) : variable.inputType === 'select' && variable.options ? (
|
|
280
|
+
<select
|
|
281
|
+
id={`var-${variable.id}`}
|
|
282
|
+
value={variable.value}
|
|
283
|
+
onChange={e => handleVariableChange(variable.id, e.target.value)}
|
|
284
|
+
className={`w-full px-3 py-2 border ${
|
|
285
|
+
errors[variable.id]
|
|
286
|
+
? 'border-red-500 focus:ring-red-500'
|
|
287
|
+
: 'border-gray-300 dark:border-gray-600 focus:ring-blue-500'
|
|
288
|
+
} rounded-md focus:outline-none focus:ring-2`}
|
|
289
|
+
required={variable.required}
|
|
290
|
+
>
|
|
291
|
+
<option value="">Select an option</option>
|
|
292
|
+
{variable.options.map(option => (
|
|
293
|
+
<option key={option} value={option}>{option}</option>
|
|
294
|
+
))}
|
|
295
|
+
</select>
|
|
296
|
+
) : (
|
|
297
|
+
<input
|
|
298
|
+
id={`var-${variable.id}`}
|
|
299
|
+
type={variable.inputType}
|
|
300
|
+
value={variable.value}
|
|
301
|
+
onChange={e => handleVariableChange(variable.id, e.target.value)}
|
|
302
|
+
className={`w-full px-3 py-2 border ${
|
|
303
|
+
errors[variable.id]
|
|
304
|
+
? 'border-red-500 focus:ring-red-500'
|
|
305
|
+
: 'border-gray-300 dark:border-gray-600 focus:ring-blue-500'
|
|
306
|
+
} rounded-md focus:outline-none focus:ring-2`}
|
|
307
|
+
required={variable.required}
|
|
308
|
+
/>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{errors[variable.id] && (
|
|
312
|
+
<p className="mt-1 text-sm text-red-600 dark:text-red-500">
|
|
313
|
+
{errors[variable.id]}
|
|
314
|
+
</p>
|
|
315
|
+
)}
|
|
316
|
+
</div>
|
|
317
|
+
))}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
})}
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<div className="mt-6 flex space-x-4">
|
|
325
|
+
<button
|
|
326
|
+
onClick={() => setActiveStep('sections')}
|
|
327
|
+
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}`}
|
|
328
|
+
>
|
|
329
|
+
Back to Sections
|
|
330
|
+
</button>
|
|
331
|
+
<button
|
|
332
|
+
onClick={generatePolicy}
|
|
333
|
+
className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName}`}
|
|
334
|
+
>
|
|
335
|
+
{generateButtonText}
|
|
336
|
+
</button>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Render policy preview
|
|
343
|
+
const renderPolicyPreview = () => {
|
|
344
|
+
return (
|
|
345
|
+
<div>
|
|
346
|
+
<h3 className="text-lg font-medium mb-4">Preview Generated Policy</h3>
|
|
347
|
+
|
|
348
|
+
{allowEditing ? (
|
|
349
|
+
<div className="mb-4">
|
|
350
|
+
<label htmlFor="policy-content" className="block text-sm font-medium mb-1">
|
|
351
|
+
Edit Policy Content
|
|
352
|
+
</label>
|
|
353
|
+
<textarea
|
|
354
|
+
id="policy-content"
|
|
355
|
+
value={editedPolicy}
|
|
356
|
+
onChange={e => setEditedPolicy(e.target.value)}
|
|
357
|
+
rows={20}
|
|
358
|
+
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-sm"
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
) : (
|
|
362
|
+
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-md mb-4">
|
|
363
|
+
<div className="prose dark:prose-invert max-w-none">
|
|
364
|
+
{generatedPolicy.split('\n').map((line, index) => {
|
|
365
|
+
if (line.startsWith('## ')) {
|
|
366
|
+
return <h2 key={index} className="text-xl font-bold mt-6 mb-3">{line.substring(3)}</h2>;
|
|
367
|
+
} else if (line.startsWith('### ')) {
|
|
368
|
+
return <h3 key={index} className="text-lg font-bold mt-4 mb-2">{line.substring(4)}</h3>;
|
|
369
|
+
} else if (line === '') {
|
|
370
|
+
return <br key={index} />;
|
|
371
|
+
} else {
|
|
372
|
+
return <p key={index} className="mb-2">{line}</p>;
|
|
373
|
+
}
|
|
374
|
+
})}
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
378
|
+
|
|
379
|
+
<div className="mt-6 flex space-x-4">
|
|
380
|
+
<button
|
|
381
|
+
onClick={() => setActiveStep('variables')}
|
|
382
|
+
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}`}
|
|
383
|
+
>
|
|
384
|
+
Back to Variables
|
|
385
|
+
</button>
|
|
386
|
+
<button
|
|
387
|
+
onClick={handleSubmit}
|
|
388
|
+
className={`px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 ${buttonClassName}`}
|
|
389
|
+
>
|
|
390
|
+
Save Policy
|
|
391
|
+
</button>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Render the current step
|
|
398
|
+
const renderStep = () => {
|
|
399
|
+
switch (activeStep) {
|
|
400
|
+
case 'sections':
|
|
401
|
+
return renderSectionList();
|
|
402
|
+
case 'variables':
|
|
403
|
+
return renderVariableForm();
|
|
404
|
+
case 'preview':
|
|
405
|
+
return renderPolicyPreview();
|
|
406
|
+
default:
|
|
407
|
+
return renderSectionList();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
return (
|
|
412
|
+
<div className={`bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md ${className}`}>
|
|
413
|
+
<h2 className="text-xl font-bold mb-2">{title}</h2>
|
|
414
|
+
<p className="mb-6 text-gray-600 dark:text-gray-300">{description}</p>
|
|
415
|
+
|
|
416
|
+
{/* Steps Indicator */}
|
|
417
|
+
<div className="mb-8">
|
|
418
|
+
<ol className="flex items-center w-full">
|
|
419
|
+
<li className={`flex w-full items-center ${activeStep === 'sections' ? 'text-blue-600 dark:text-blue-500' : 'text-gray-500 dark:text-gray-400'} after:content-[''] after:w-full after:h-1 after:border-b after:border-gray-200 after:border-1 after:hidden sm:after:inline-block after:mx-6 xl:after:mx-10`}>
|
|
420
|
+
<span className={`flex items-center justify-center w-8 h-8 ${activeStep === 'sections' ? 'bg-blue-100 dark:bg-blue-800' : 'bg-gray-100 dark:bg-gray-700'} rounded-full shrink-0`}>
|
|
421
|
+
1
|
|
422
|
+
</span>
|
|
423
|
+
<span className="hidden sm:inline-flex sm:ml-2">Sections</span>
|
|
424
|
+
</li>
|
|
425
|
+
<li className={`flex w-full items-center ${activeStep === 'variables' ? 'text-blue-600 dark:text-blue-500' : 'text-gray-500 dark:text-gray-400'} after:content-[''] after:w-full after:h-1 after:border-b after:border-gray-200 after:border-1 after:hidden sm:after:inline-block after:mx-6 xl:after:mx-10`}>
|
|
426
|
+
<span className={`flex items-center justify-center w-8 h-8 ${activeStep === 'variables' ? 'bg-blue-100 dark:bg-blue-800' : 'bg-gray-100 dark:bg-gray-700'} rounded-full shrink-0`}>
|
|
427
|
+
2
|
|
428
|
+
</span>
|
|
429
|
+
<span className="hidden sm:inline-flex sm:ml-2">Variables</span>
|
|
430
|
+
</li>
|
|
431
|
+
<li className={`flex items-center ${activeStep === 'preview' ? 'text-blue-600 dark:text-blue-500' : 'text-gray-500 dark:text-gray-400'}`}>
|
|
432
|
+
<span className={`flex items-center justify-center w-8 h-8 ${activeStep === 'preview' ? 'bg-blue-100 dark:bg-blue-800' : 'bg-gray-100 dark:bg-gray-700'} rounded-full shrink-0`}>
|
|
433
|
+
3
|
|
434
|
+
</span>
|
|
435
|
+
<span className="hidden sm:inline-flex sm:ml-2">Preview</span>
|
|
436
|
+
</li>
|
|
437
|
+
</ol>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
{/* NDPR Notice */}
|
|
441
|
+
<div className="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-md">
|
|
442
|
+
<h3 className="text-sm font-bold text-blue-800 dark:text-blue-200 mb-2">NDPR Compliance Notice</h3>
|
|
443
|
+
<p className="text-blue-700 dark:text-blue-300 text-sm">
|
|
444
|
+
This tool helps you generate a privacy policy that aligns with the Nigeria Data Protection Regulation (NDPR).
|
|
445
|
+
While we strive to ensure compliance, we recommend having the final policy reviewed by a legal professional
|
|
446
|
+
familiar with NDPR requirements.
|
|
447
|
+
</p>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
{/* Current Step Content */}
|
|
451
|
+
{renderStep()}
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
};
|