@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.
Files changed (212) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/.eslintrc.json +10 -0
  3. package/.github/workflows/ci.yml +36 -0
  4. package/.github/workflows/nextjs.yml +104 -0
  5. package/.husky/commit-msg +4 -0
  6. package/.husky/pre-commit +4 -0
  7. package/.lintstagedrc.js +4 -0
  8. package/.nvmrc +1 -0
  9. package/.versionrc +17 -0
  10. package/CHANGELOG.md +16 -0
  11. package/CLAUDE.md +90 -0
  12. package/CNAME +1 -0
  13. package/CONTRIBUTING.md +87 -0
  14. package/README.md +84 -447
  15. package/RELEASE-NOTES-v1.0.0.md +140 -0
  16. package/RELEASE-NOTES-v1.0.1.md +69 -0
  17. package/SECURITY.md +21 -0
  18. package/commitlint.config.js +36 -0
  19. package/components.json +21 -0
  20. package/eslint.config.mjs +16 -0
  21. package/jest.config.js +31 -0
  22. package/jest.setup.js +15 -0
  23. package/next.config.js +15 -0
  24. package/next.config.ts +62 -0
  25. package/package.json +70 -52
  26. package/packages/ndpr-toolkit/README.md +467 -0
  27. package/packages/ndpr-toolkit/jest.config.js +23 -0
  28. package/packages/ndpr-toolkit/package-lock.json +8197 -0
  29. package/packages/ndpr-toolkit/package.json +71 -0
  30. package/packages/ndpr-toolkit/rollup.config.js +34 -0
  31. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +119 -0
  32. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +122 -0
  33. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +270 -0
  34. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +199 -0
  35. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +224 -0
  36. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +104 -0
  37. package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +161 -0
  38. package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +330 -0
  39. package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +149 -0
  40. package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +88 -0
  41. package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +160 -0
  42. package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +110 -0
  43. package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +97 -0
  44. package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +701 -0
  45. package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +631 -0
  46. package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +569 -0
  47. package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +496 -0
  48. package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +270 -0
  49. package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +217 -0
  50. package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +206 -0
  51. package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +342 -0
  52. package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +373 -0
  53. package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +174 -0
  54. package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +717 -0
  55. package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +476 -0
  56. package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +620 -0
  57. package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +541 -0
  58. package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +454 -0
  59. package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +333 -0
  60. package/packages/ndpr-toolkit/src/hooks/useBreach.ts +409 -0
  61. package/packages/ndpr-toolkit/src/hooks/useConsent.ts +263 -0
  62. package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +457 -0
  63. package/packages/ndpr-toolkit/src/hooks/useDSR.ts +236 -0
  64. package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +428 -0
  65. package/{dist/index.d.ts → packages/ndpr-toolkit/src/index.ts} +13 -0
  66. package/packages/ndpr-toolkit/src/setupTests.ts +5 -0
  67. package/packages/ndpr-toolkit/src/types/breach.ts +283 -0
  68. package/packages/ndpr-toolkit/src/types/consent.ts +111 -0
  69. package/packages/ndpr-toolkit/src/types/dpia.ts +236 -0
  70. package/packages/ndpr-toolkit/src/types/dsr.ts +192 -0
  71. package/packages/ndpr-toolkit/src/types/index.ts +42 -0
  72. package/packages/ndpr-toolkit/src/types/privacy.ts +246 -0
  73. package/packages/ndpr-toolkit/src/utils/breach.ts +122 -0
  74. package/packages/ndpr-toolkit/src/utils/consent.ts +51 -0
  75. package/packages/ndpr-toolkit/src/utils/dpia.ts +104 -0
  76. package/packages/ndpr-toolkit/src/utils/dsr.ts +77 -0
  77. package/packages/ndpr-toolkit/src/utils/privacy.ts +100 -0
  78. package/packages/ndpr-toolkit/tsconfig.json +23 -0
  79. package/postcss.config.mjs +5 -0
  80. package/public/NDPR TOOLKIT.svg +1 -0
  81. package/public/favicon/android-chrome-192x192.png +0 -0
  82. package/public/favicon/android-chrome-512x512.png +0 -0
  83. package/public/favicon/apple-touch-icon.png +0 -0
  84. package/public/favicon/favicon-16x16.png +0 -0
  85. package/public/favicon/favicon-32x32.png +0 -0
  86. package/public/favicon/site.webmanifest +1 -0
  87. package/public/file.svg +1 -0
  88. package/public/globe.svg +1 -0
  89. package/public/ndpr-toolkit-logo.svg +108 -0
  90. package/public/next.svg +1 -0
  91. package/public/vercel.svg +1 -0
  92. package/public/window.svg +1 -0
  93. package/src/__tests__/example.test.ts +13 -0
  94. package/src/__tests__/requestService.test.ts +57 -0
  95. package/src/app/accessibility.css +70 -0
  96. package/src/app/docs/components/DocLayout.tsx +267 -0
  97. package/src/app/docs/components/breach-notification/page.tsx +797 -0
  98. package/src/app/docs/components/consent-management/page.tsx +576 -0
  99. package/src/app/docs/components/data-subject-rights/page.tsx +511 -0
  100. package/src/app/docs/components/dpia-questionnaire/layout.tsx +15 -0
  101. package/src/app/docs/components/dpia-questionnaire/metadata.ts +31 -0
  102. package/src/app/docs/components/dpia-questionnaire/page.tsx +666 -0
  103. package/src/app/docs/components/hooks/page.tsx +305 -0
  104. package/src/app/docs/components/page.tsx +84 -0
  105. package/src/app/docs/components/privacy-policy-generator/page.tsx +634 -0
  106. package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +123 -0
  107. package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +328 -0
  108. package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +28 -0
  109. package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +91 -0
  110. package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +118 -0
  111. package/src/app/docs/guides/breach-notification-process/page.tsx +39 -0
  112. package/src/app/docs/guides/conducting-dpia/page.tsx +593 -0
  113. package/src/app/docs/guides/data-subject-requests/page.tsx +666 -0
  114. package/src/app/docs/guides/managing-consent/page.tsx +738 -0
  115. package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +296 -0
  116. package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +145 -0
  117. package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +33 -0
  118. package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +99 -0
  119. package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +159 -0
  120. package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +38 -0
  121. package/src/app/docs/guides/page.tsx +67 -0
  122. package/src/app/docs/layout.tsx +15 -0
  123. package/src/app/docs/metadata.ts +31 -0
  124. package/src/app/docs/page.tsx +572 -0
  125. package/src/app/favicon.ico +0 -0
  126. package/src/app/globals.css +123 -0
  127. package/src/app/layout.tsx +37 -0
  128. package/src/app/ndpr-demos/breach/page.tsx +354 -0
  129. package/src/app/ndpr-demos/consent/page.tsx +366 -0
  130. package/src/app/ndpr-demos/dpia/page.tsx +495 -0
  131. package/src/app/ndpr-demos/dsr/page.tsx +280 -0
  132. package/src/app/ndpr-demos/page.tsx +73 -0
  133. package/src/app/ndpr-demos/policy/page.tsx +771 -0
  134. package/src/app/page.tsx +452 -0
  135. package/src/components/ErrorBoundary.tsx +90 -0
  136. package/src/components/breach-notification/BreachNotificationForm.tsx +479 -0
  137. package/src/components/consent/ConsentBanner.tsx +159 -0
  138. package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +419 -0
  139. package/src/components/docs/DocLayout.tsx +289 -0
  140. package/src/components/docs/index.ts +2 -0
  141. package/src/components/dpia/DPIAQuestionnaire.tsx +483 -0
  142. package/src/components/privacy-policy/PolicyGenerator.tsx +1062 -0
  143. package/src/components/privacy-policy/data.ts +98 -0
  144. package/src/components/privacy-policy/shared/CheckboxField.tsx +38 -0
  145. package/src/components/privacy-policy/shared/CheckboxGroup.tsx +85 -0
  146. package/src/components/privacy-policy/shared/FormField.tsx +79 -0
  147. package/src/components/privacy-policy/shared/StepIndicator.tsx +86 -0
  148. package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +335 -0
  149. package/src/components/privacy-policy/steps/DataCollectionStep.tsx +231 -0
  150. package/src/components/privacy-policy/steps/DataSharingStep.tsx +418 -0
  151. package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +202 -0
  152. package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +172 -0
  153. package/src/components/ui/Badge.tsx +46 -0
  154. package/src/components/ui/Button.tsx +59 -0
  155. package/src/components/ui/Card.tsx +92 -0
  156. package/src/components/ui/Checkbox.tsx +57 -0
  157. package/src/components/ui/FormField.tsx +50 -0
  158. package/src/components/ui/Input.tsx +38 -0
  159. package/src/components/ui/Loading.tsx +201 -0
  160. package/src/components/ui/Select.tsx +42 -0
  161. package/src/components/ui/TextArea.tsx +38 -0
  162. package/src/components/ui/label.tsx +24 -0
  163. package/src/components/ui/switch.tsx +31 -0
  164. package/src/components/ui/tabs.tsx +66 -0
  165. package/src/hooks/useConsent.ts +64 -0
  166. package/src/hooks/useLoadingState.ts +85 -0
  167. package/src/lib/consentService.ts +137 -0
  168. package/src/lib/dpiaQuestions.ts +148 -0
  169. package/src/lib/requestService.ts +75 -0
  170. package/src/lib/sanitize.ts +108 -0
  171. package/src/lib/storage.ts +222 -0
  172. package/src/lib/utils.ts +6 -0
  173. package/src/types/html-to-docx.d.ts +30 -0
  174. package/src/types/index.ts +72 -0
  175. package/tailwind.config.ts +65 -0
  176. package/tsconfig.json +41 -0
  177. package/dist/components/breach/BreachNotificationManager.d.ts +0 -62
  178. package/dist/components/breach/BreachReportForm.d.ts +0 -66
  179. package/dist/components/breach/BreachRiskAssessment.d.ts +0 -50
  180. package/dist/components/breach/RegulatoryReportGenerator.d.ts +0 -94
  181. package/dist/components/consent/ConsentBanner.d.ts +0 -79
  182. package/dist/components/consent/ConsentManager.d.ts +0 -73
  183. package/dist/components/consent/ConsentStorage.d.ts +0 -41
  184. package/dist/components/dpia/DPIAQuestionnaire.d.ts +0 -70
  185. package/dist/components/dpia/DPIAReport.d.ts +0 -40
  186. package/dist/components/dpia/StepIndicator.d.ts +0 -64
  187. package/dist/components/dsr/DSRDashboard.d.ts +0 -58
  188. package/dist/components/dsr/DSRRequestForm.d.ts +0 -74
  189. package/dist/components/dsr/DSRTracker.d.ts +0 -56
  190. package/dist/components/policy/PolicyExporter.d.ts +0 -65
  191. package/dist/components/policy/PolicyGenerator.d.ts +0 -54
  192. package/dist/components/policy/PolicyPreview.d.ts +0 -71
  193. package/dist/hooks/useBreach.d.ts +0 -97
  194. package/dist/hooks/useConsent.d.ts +0 -63
  195. package/dist/hooks/useDPIA.d.ts +0 -92
  196. package/dist/hooks/useDSR.d.ts +0 -72
  197. package/dist/hooks/usePrivacyPolicy.d.ts +0 -87
  198. package/dist/index.esm.js +0 -2
  199. package/dist/index.esm.js.map +0 -1
  200. package/dist/index.js +0 -2
  201. package/dist/index.js.map +0 -1
  202. package/dist/setupTests.d.ts +0 -2
  203. package/dist/types/breach.d.ts +0 -239
  204. package/dist/types/consent.d.ts +0 -95
  205. package/dist/types/dpia.d.ts +0 -196
  206. package/dist/types/dsr.d.ts +0 -162
  207. package/dist/types/privacy.d.ts +0 -204
  208. package/dist/utils/breach.d.ts +0 -14
  209. package/dist/utils/consent.d.ts +0 -10
  210. package/dist/utils/dpia.d.ts +0 -12
  211. package/dist/utils/dsr.d.ts +0 -11
  212. 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
+ };