@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.
Files changed (156) hide show
  1. package/next-env.d.ts +5 -0
  2. package/package.json +1 -1
  3. package/.claude/settings.local.json +0 -20
  4. package/.eslintrc.json +0 -10
  5. package/.github/workflows/ci.yml +0 -36
  6. package/.github/workflows/nextjs.yml +0 -104
  7. package/.husky/commit-msg +0 -4
  8. package/.husky/pre-commit +0 -4
  9. package/.lintstagedrc.js +0 -4
  10. package/.nvmrc +0 -1
  11. package/.versionrc +0 -17
  12. package/CLAUDE.md +0 -90
  13. package/commitlint.config.js +0 -36
  14. package/eslint.config.mjs +0 -16
  15. package/jest.config.js +0 -31
  16. package/jest.setup.js +0 -15
  17. package/next.config.js +0 -15
  18. package/next.config.ts +0 -62
  19. package/packages/ndpr-toolkit/README.md +0 -467
  20. package/packages/ndpr-toolkit/jest.config.js +0 -23
  21. package/packages/ndpr-toolkit/package-lock.json +0 -8197
  22. package/packages/ndpr-toolkit/package.json +0 -71
  23. package/packages/ndpr-toolkit/rollup.config.js +0 -34
  24. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +0 -119
  25. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +0 -122
  26. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +0 -270
  27. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +0 -199
  28. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +0 -224
  29. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +0 -104
  30. package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +0 -161
  31. package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +0 -330
  32. package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +0 -149
  33. package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +0 -88
  34. package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +0 -160
  35. package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +0 -110
  36. package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +0 -97
  37. package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +0 -701
  38. package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +0 -631
  39. package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +0 -569
  40. package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +0 -496
  41. package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +0 -270
  42. package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +0 -217
  43. package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +0 -206
  44. package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +0 -342
  45. package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +0 -373
  46. package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +0 -174
  47. package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +0 -717
  48. package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +0 -476
  49. package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +0 -620
  50. package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +0 -541
  51. package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +0 -454
  52. package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +0 -333
  53. package/packages/ndpr-toolkit/src/hooks/useBreach.ts +0 -409
  54. package/packages/ndpr-toolkit/src/hooks/useConsent.ts +0 -263
  55. package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +0 -457
  56. package/packages/ndpr-toolkit/src/hooks/useDSR.ts +0 -236
  57. package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +0 -428
  58. package/packages/ndpr-toolkit/src/index.ts +0 -44
  59. package/packages/ndpr-toolkit/src/setupTests.ts +0 -5
  60. package/packages/ndpr-toolkit/src/types/breach.ts +0 -283
  61. package/packages/ndpr-toolkit/src/types/consent.ts +0 -111
  62. package/packages/ndpr-toolkit/src/types/dpia.ts +0 -236
  63. package/packages/ndpr-toolkit/src/types/dsr.ts +0 -192
  64. package/packages/ndpr-toolkit/src/types/index.ts +0 -42
  65. package/packages/ndpr-toolkit/src/types/privacy.ts +0 -246
  66. package/packages/ndpr-toolkit/src/utils/breach.ts +0 -122
  67. package/packages/ndpr-toolkit/src/utils/consent.ts +0 -51
  68. package/packages/ndpr-toolkit/src/utils/dpia.ts +0 -104
  69. package/packages/ndpr-toolkit/src/utils/dsr.ts +0 -77
  70. package/packages/ndpr-toolkit/src/utils/privacy.ts +0 -100
  71. package/packages/ndpr-toolkit/tsconfig.json +0 -23
  72. package/postcss.config.mjs +0 -5
  73. package/src/__tests__/example.test.ts +0 -13
  74. package/src/__tests__/requestService.test.ts +0 -57
  75. package/src/app/accessibility.css +0 -70
  76. package/src/app/docs/components/DocLayout.tsx +0 -267
  77. package/src/app/docs/components/breach-notification/page.tsx +0 -797
  78. package/src/app/docs/components/consent-management/page.tsx +0 -576
  79. package/src/app/docs/components/data-subject-rights/page.tsx +0 -511
  80. package/src/app/docs/components/dpia-questionnaire/layout.tsx +0 -15
  81. package/src/app/docs/components/dpia-questionnaire/metadata.ts +0 -31
  82. package/src/app/docs/components/dpia-questionnaire/page.tsx +0 -666
  83. package/src/app/docs/components/hooks/page.tsx +0 -305
  84. package/src/app/docs/components/page.tsx +0 -84
  85. package/src/app/docs/components/privacy-policy-generator/page.tsx +0 -634
  86. package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +0 -123
  87. package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +0 -328
  88. package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +0 -28
  89. package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +0 -91
  90. package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +0 -118
  91. package/src/app/docs/guides/breach-notification-process/page.tsx +0 -39
  92. package/src/app/docs/guides/conducting-dpia/page.tsx +0 -593
  93. package/src/app/docs/guides/data-subject-requests/page.tsx +0 -666
  94. package/src/app/docs/guides/managing-consent/page.tsx +0 -738
  95. package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +0 -296
  96. package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +0 -145
  97. package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +0 -33
  98. package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +0 -99
  99. package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +0 -159
  100. package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +0 -38
  101. package/src/app/docs/guides/page.tsx +0 -67
  102. package/src/app/docs/layout.tsx +0 -15
  103. package/src/app/docs/metadata.ts +0 -31
  104. package/src/app/docs/page.tsx +0 -572
  105. package/src/app/favicon.ico +0 -0
  106. package/src/app/globals.css +0 -123
  107. package/src/app/layout.tsx +0 -37
  108. package/src/app/ndpr-demos/breach/page.tsx +0 -354
  109. package/src/app/ndpr-demos/consent/page.tsx +0 -366
  110. package/src/app/ndpr-demos/dpia/page.tsx +0 -495
  111. package/src/app/ndpr-demos/dsr/page.tsx +0 -280
  112. package/src/app/ndpr-demos/page.tsx +0 -73
  113. package/src/app/ndpr-demos/policy/page.tsx +0 -771
  114. package/src/app/page.tsx +0 -452
  115. package/src/components/ErrorBoundary.tsx +0 -90
  116. package/src/components/breach-notification/BreachNotificationForm.tsx +0 -479
  117. package/src/components/consent/ConsentBanner.tsx +0 -159
  118. package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +0 -419
  119. package/src/components/docs/DocLayout.tsx +0 -289
  120. package/src/components/docs/index.ts +0 -2
  121. package/src/components/dpia/DPIAQuestionnaire.tsx +0 -483
  122. package/src/components/privacy-policy/PolicyGenerator.tsx +0 -1062
  123. package/src/components/privacy-policy/data.ts +0 -98
  124. package/src/components/privacy-policy/shared/CheckboxField.tsx +0 -38
  125. package/src/components/privacy-policy/shared/CheckboxGroup.tsx +0 -85
  126. package/src/components/privacy-policy/shared/FormField.tsx +0 -79
  127. package/src/components/privacy-policy/shared/StepIndicator.tsx +0 -86
  128. package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +0 -335
  129. package/src/components/privacy-policy/steps/DataCollectionStep.tsx +0 -231
  130. package/src/components/privacy-policy/steps/DataSharingStep.tsx +0 -418
  131. package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +0 -202
  132. package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +0 -172
  133. package/src/components/ui/Badge.tsx +0 -46
  134. package/src/components/ui/Button.tsx +0 -59
  135. package/src/components/ui/Card.tsx +0 -92
  136. package/src/components/ui/Checkbox.tsx +0 -57
  137. package/src/components/ui/FormField.tsx +0 -50
  138. package/src/components/ui/Input.tsx +0 -38
  139. package/src/components/ui/Loading.tsx +0 -201
  140. package/src/components/ui/Select.tsx +0 -42
  141. package/src/components/ui/TextArea.tsx +0 -38
  142. package/src/components/ui/label.tsx +0 -24
  143. package/src/components/ui/switch.tsx +0 -31
  144. package/src/components/ui/tabs.tsx +0 -66
  145. package/src/hooks/useConsent.ts +0 -64
  146. package/src/hooks/useLoadingState.ts +0 -85
  147. package/src/lib/consentService.ts +0 -137
  148. package/src/lib/dpiaQuestions.ts +0 -148
  149. package/src/lib/requestService.ts +0 -75
  150. package/src/lib/sanitize.ts +0 -108
  151. package/src/lib/storage.ts +0 -222
  152. package/src/lib/utils.ts +0 -6
  153. package/src/types/html-to-docx.d.ts +0 -30
  154. package/src/types/index.ts +0 -72
  155. package/tailwind.config.ts +0 -65
  156. package/tsconfig.json +0 -41
@@ -1,541 +0,0 @@
1
- import React, { useState } from 'react';
2
-
3
- export interface PolicyExporterProps {
4
- /**
5
- * The policy content to export
6
- */
7
- content: string;
8
-
9
- /**
10
- * The policy title
11
- */
12
- title?: string;
13
-
14
- /**
15
- * The organization name to include in the exported policy
16
- */
17
- organizationName?: string;
18
-
19
- /**
20
- * The last updated date to include in the exported policy
21
- */
22
- lastUpdated?: Date;
23
-
24
- /**
25
- * Callback function called when the export is complete
26
- */
27
- onExportComplete?: (format: string, url: string) => void;
28
-
29
- /**
30
- * Title displayed on the exporter
31
- * @default "Export Privacy Policy"
32
- */
33
- componentTitle?: string;
34
-
35
- /**
36
- * Description text displayed on the exporter
37
- * @default "Export your NDPR-compliant privacy policy in various formats."
38
- */
39
- description?: string;
40
-
41
- /**
42
- * Custom CSS class for the exporter
43
- */
44
- className?: string;
45
-
46
- /**
47
- * Custom CSS class for the buttons
48
- */
49
- buttonClassName?: string;
50
-
51
- /**
52
- * Whether to show the export history
53
- * @default true
54
- */
55
- showExportHistory?: boolean;
56
-
57
- /**
58
- * Whether to include the NDPR compliance notice in the exported policy
59
- * @default true
60
- */
61
- includeComplianceNotice?: boolean;
62
-
63
- /**
64
- * Whether to include the organization logo in the exported policy
65
- * @default false
66
- */
67
- includeLogo?: boolean;
68
-
69
- /**
70
- * URL of the organization logo
71
- */
72
- logoUrl?: string;
73
-
74
- /**
75
- * Custom CSS styles for the exported policy
76
- */
77
- customStyles?: string;
78
- }
79
-
80
- interface ExportRecord {
81
- id: string;
82
- format: string;
83
- timestamp: number;
84
- url: string;
85
- filename: string;
86
- }
87
-
88
- export const PolicyExporter: React.FC<PolicyExporterProps> = ({
89
- content,
90
- title = "Privacy Policy",
91
- organizationName,
92
- lastUpdated = new Date(),
93
- onExportComplete,
94
- componentTitle = "Export Privacy Policy",
95
- description = "Export your NDPR-compliant privacy policy in various formats.",
96
- className = "",
97
- buttonClassName = "",
98
- showExportHistory = true,
99
- includeComplianceNotice = true,
100
- includeLogo = false,
101
- logoUrl,
102
- customStyles
103
- }) => {
104
- const [exportHistory, setExportHistory] = useState<ExportRecord[]>([]);
105
- const [selectedFormat, setSelectedFormat] = useState<string>('pdf');
106
- const [isExporting, setIsExporting] = useState<boolean>(false);
107
- const [exportError, setExportError] = useState<string | null>(null);
108
- const [customFilename, setCustomFilename] = useState<string>('');
109
- const [customHeader, setCustomHeader] = useState<string>('');
110
- const [customFooter, setCustomFooter] = useState<string>('');
111
- const [showAdvancedOptions, setShowAdvancedOptions] = useState<boolean>(false);
112
-
113
- // Generate a default filename based on the organization name and format
114
- const generateDefaultFilename = (format: string): string => {
115
- const dateStr = new Date().toISOString().split('T')[0];
116
- const orgStr = organizationName ?
117
- organizationName.toLowerCase().replace(/[^a-z0-9]+/g, '-') :
118
- 'privacy-policy';
119
-
120
- return `${orgStr}-privacy-policy-${dateStr}.${format.toLowerCase()}`;
121
- };
122
-
123
- // Get the actual filename to use
124
- const getFilename = (format: string): string => {
125
- if (customFilename) {
126
- // Ensure the filename has the correct extension
127
- if (customFilename.endsWith(`.${format.toLowerCase()}`)) {
128
- return customFilename;
129
- } else {
130
- return `${customFilename}.${format.toLowerCase()}`;
131
- }
132
- }
133
-
134
- return generateDefaultFilename(format);
135
- };
136
-
137
- // Generate HTML content for export
138
- const generateHTMLContent = (): string => {
139
- const fullTitle = organizationName ? `${organizationName} ${title}` : title;
140
- const dateStr = lastUpdated.toLocaleDateString();
141
-
142
- let html = `<!DOCTYPE html>
143
- <html lang="en">
144
- <head>
145
- <meta charset="UTF-8">
146
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
147
- <title>${fullTitle}</title>
148
- <style>
149
- body {
150
- font-family: Arial, sans-serif;
151
- line-height: 1.6;
152
- color: #333;
153
- max-width: 800px;
154
- margin: 0 auto;
155
- padding: 20px;
156
- }
157
- h1 {
158
- font-size: 24px;
159
- margin-bottom: 10px;
160
- }
161
- h2 {
162
- font-size: 20px;
163
- margin-top: 30px;
164
- margin-bottom: 10px;
165
- border-bottom: 1px solid #eee;
166
- padding-bottom: 5px;
167
- }
168
- h3 {
169
- font-size: 18px;
170
- margin-top: 20px;
171
- margin-bottom: 10px;
172
- }
173
- p {
174
- margin-bottom: 15px;
175
- }
176
- .header {
177
- text-align: center;
178
- margin-bottom: 30px;
179
- }
180
- .footer {
181
- margin-top: 50px;
182
- text-align: center;
183
- font-size: 12px;
184
- color: #666;
185
- border-top: 1px solid #eee;
186
- padding-top: 20px;
187
- }
188
- .logo {
189
- max-width: 200px;
190
- margin-bottom: 20px;
191
- }
192
- .last-updated {
193
- font-size: 12px;
194
- color: #666;
195
- margin-bottom: 30px;
196
- }
197
- .compliance-notice {
198
- background-color: #f8f9fa;
199
- border: 1px solid #e9ecef;
200
- padding: 15px;
201
- margin-bottom: 30px;
202
- font-size: 14px;
203
- }
204
- ${customStyles || ''}
205
- </style>
206
- </head>
207
- <body>
208
- <div class="header">
209
- ${includeLogo && logoUrl ? `<img src="${logoUrl}" alt="${organizationName || 'Company'} Logo" class="logo">` : ''}
210
- ${customHeader ? `<div class="custom-header">${customHeader}</div>` : ''}
211
- <h1>${fullTitle}</h1>
212
- <div class="last-updated">Last Updated: ${dateStr}</div>
213
- </div>`;
214
-
215
- if (includeComplianceNotice) {
216
- html += `
217
- <div class="compliance-notice">
218
- <strong>NDPR Compliance Notice:</strong> This privacy policy has been created to comply with the Nigeria Data Protection Regulation (NDPR).
219
- It outlines how we collect, use, disclose, and protect your personal information in accordance with NDPR requirements.
220
- </div>`;
221
- }
222
-
223
- // Convert markdown content to HTML
224
- const htmlContent = content
225
- .replace(/^## (.*?)$/gm, '<h2>$1</h2>')
226
- .replace(/^### (.*?)$/gm, '<h3>$1</h3>')
227
- .replace(/\n\n/g, '</p><p>')
228
- .replace(/\n/g, '<br>');
229
-
230
- html += `
231
- <div class="content">
232
- <p>${htmlContent}</p>
233
- </div>
234
-
235
- <div class="footer">
236
- ${customFooter ? `<div class="custom-footer">${customFooter}</div>` : ''}
237
- <p>&copy; ${new Date().getFullYear()} ${organizationName || 'Company'}. All rights reserved.</p>
238
- </div>
239
- </body>
240
- </html>`;
241
-
242
- return html;
243
- };
244
-
245
- // Handle export button click
246
- const handleExport = async () => {
247
- setIsExporting(true);
248
- setExportError(null);
249
-
250
- try {
251
- const format = selectedFormat.toLowerCase();
252
- let url = '';
253
- let blob: Blob;
254
-
255
- switch (format) {
256
- case 'pdf':
257
- // In a real implementation, this would use a PDF generation library
258
- // For this example, we'll just create an HTML file with a note
259
- const htmlForPdf = generateHTMLContent();
260
- blob = new Blob([htmlForPdf], { type: 'text/html' });
261
- url = URL.createObjectURL(blob);
262
- break;
263
-
264
- case 'docx':
265
- // In a real implementation, this would use a DOCX generation library
266
- // For this example, we'll just create a text file with a note
267
- blob = new Blob([content], { type: 'text/plain' });
268
- url = URL.createObjectURL(blob);
269
- break;
270
-
271
- case 'html':
272
- const html = generateHTMLContent();
273
- blob = new Blob([html], { type: 'text/html' });
274
- url = URL.createObjectURL(blob);
275
- break;
276
-
277
- case 'markdown':
278
- default:
279
- blob = new Blob([content], { type: 'text/markdown' });
280
- url = URL.createObjectURL(blob);
281
- break;
282
- }
283
-
284
- // Create a download link and trigger it
285
- const filename = getFilename(format);
286
- const element = document.createElement('a');
287
- element.href = url;
288
- element.download = filename;
289
- document.body.appendChild(element);
290
- element.click();
291
- document.body.removeChild(element);
292
-
293
- // Add to export history
294
- const exportRecord: ExportRecord = {
295
- id: `export_${Date.now()}`,
296
- format,
297
- timestamp: Date.now(),
298
- url,
299
- filename
300
- };
301
-
302
- setExportHistory(prevHistory => [exportRecord, ...prevHistory]);
303
-
304
- // Call the callback
305
- if (onExportComplete) {
306
- onExportComplete(format, url);
307
- }
308
- } catch (error) {
309
- console.error('Export error:', error);
310
- setExportError('An error occurred during export. Please try again.');
311
- } finally {
312
- setIsExporting(false);
313
- }
314
- };
315
-
316
- // Render export format options
317
- const renderFormatOptions = () => {
318
- const formats = [
319
- { value: 'pdf', label: 'PDF Document (.pdf)' },
320
- { value: 'docx', label: 'Word Document (.docx)' },
321
- { value: 'html', label: 'Web Page (.html)' },
322
- { value: 'markdown', label: 'Markdown (.md)' }
323
- ];
324
-
325
- return (
326
- <div className="mb-6">
327
- <label htmlFor="export-format" className="block text-sm font-medium mb-1">
328
- Export Format
329
- </label>
330
- <select
331
- id="export-format"
332
- value={selectedFormat}
333
- onChange={e => setSelectedFormat(e.target.value)}
334
- 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"
335
- >
336
- {formats.map(format => (
337
- <option key={format.value} value={format.value}>
338
- {format.label}
339
- </option>
340
- ))}
341
- </select>
342
- </div>
343
- );
344
- };
345
-
346
- // Render advanced options
347
- const renderAdvancedOptions = () => {
348
- if (!showAdvancedOptions) {
349
- return (
350
- <button
351
- type="button"
352
- onClick={() => setShowAdvancedOptions(true)}
353
- className="text-blue-600 dark:text-blue-400 text-sm mb-6"
354
- >
355
- Show Advanced Options
356
- </button>
357
- );
358
- }
359
-
360
- return (
361
- <div className="mb-6 space-y-4 border border-gray-200 dark:border-gray-700 rounded-md p-4">
362
- <div className="flex justify-between items-center">
363
- <h3 className="text-md font-medium">Advanced Export Options</h3>
364
- <button
365
- type="button"
366
- onClick={() => setShowAdvancedOptions(false)}
367
- className="text-blue-600 dark:text-blue-400 text-sm"
368
- >
369
- Hide Advanced Options
370
- </button>
371
- </div>
372
-
373
- <div>
374
- <label htmlFor="custom-filename" className="block text-sm font-medium mb-1">
375
- Custom Filename
376
- </label>
377
- <input
378
- type="text"
379
- id="custom-filename"
380
- value={customFilename}
381
- onChange={e => setCustomFilename(e.target.value)}
382
- placeholder={generateDefaultFilename(selectedFormat)}
383
- 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"
384
- />
385
- <p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
386
- Leave blank to use the default filename format.
387
- </p>
388
- </div>
389
-
390
- <div>
391
- <label htmlFor="custom-header" className="block text-sm font-medium mb-1">
392
- Custom Header HTML (for HTML/PDF exports)
393
- </label>
394
- <textarea
395
- id="custom-header"
396
- value={customHeader}
397
- onChange={e => setCustomHeader(e.target.value)}
398
- rows={3}
399
- placeholder="<div>Custom header content</div>"
400
- 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"
401
- />
402
- </div>
403
-
404
- <div>
405
- <label htmlFor="custom-footer" className="block text-sm font-medium mb-1">
406
- Custom Footer HTML (for HTML/PDF exports)
407
- </label>
408
- <textarea
409
- id="custom-footer"
410
- value={customFooter}
411
- onChange={e => setCustomFooter(e.target.value)}
412
- rows={3}
413
- placeholder="<div>Custom footer content</div>"
414
- 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"
415
- />
416
- </div>
417
-
418
- <div className="flex items-start">
419
- <div className="flex items-center h-5">
420
- <input
421
- id="include-compliance-notice"
422
- type="checkbox"
423
- checked={includeComplianceNotice}
424
- onChange={e => setShowAdvancedOptions(e.target.checked)}
425
- 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"
426
- />
427
- </div>
428
- <div className="ml-3 text-sm">
429
- <label htmlFor="include-compliance-notice" className="font-medium text-gray-900 dark:text-white">
430
- Include NDPR Compliance Notice
431
- </label>
432
- <p className="text-gray-500 dark:text-gray-400">
433
- Adds a notice explaining that this policy complies with NDPR requirements.
434
- </p>
435
- </div>
436
- </div>
437
- </div>
438
- );
439
- };
440
-
441
- // Render export history
442
- const renderExportHistory = () => {
443
- if (!showExportHistory || exportHistory.length === 0) {
444
- return null;
445
- }
446
-
447
- return (
448
- <div className="mt-6">
449
- <h3 className="text-lg font-medium mb-3">Export History</h3>
450
- <div className="bg-gray-50 dark:bg-gray-700 rounded-md overflow-hidden">
451
- <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
452
- <thead className="bg-gray-100 dark:bg-gray-800">
453
- <tr>
454
- <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
455
- Date
456
- </th>
457
- <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
458
- Format
459
- </th>
460
- <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
461
- Filename
462
- </th>
463
- <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
464
- Actions
465
- </th>
466
- </tr>
467
- </thead>
468
- <tbody className="bg-white dark:bg-gray-700 divide-y divide-gray-200 dark:divide-gray-600">
469
- {exportHistory.map(record => (
470
- <tr key={record.id}>
471
- <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
472
- {new Date(record.timestamp).toLocaleString()}
473
- </td>
474
- <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
475
- {record.format.toUpperCase()}
476
- </td>
477
- <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">
478
- {record.filename}
479
- </td>
480
- <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
481
- <a
482
- href={record.url}
483
- download={record.filename}
484
- className="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 mr-4"
485
- >
486
- Download
487
- </a>
488
- </td>
489
- </tr>
490
- ))}
491
- </tbody>
492
- </table>
493
- </div>
494
- </div>
495
- );
496
- };
497
-
498
- return (
499
- <div className={`bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md ${className}`}>
500
- <h2 className="text-xl font-bold mb-2">{componentTitle}</h2>
501
- <p className="mb-6 text-gray-600 dark:text-gray-300">{description}</p>
502
-
503
- {/* Format Selection */}
504
- {renderFormatOptions()}
505
-
506
- {/* Advanced Options */}
507
- {renderAdvancedOptions()}
508
-
509
- {/* Export Button */}
510
- <div className="mb-6">
511
- <button
512
- onClick={handleExport}
513
- disabled={isExporting}
514
- className={`px-6 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${buttonClassName} ${isExporting ? 'opacity-70 cursor-not-allowed' : ''}`}
515
- >
516
- {isExporting ? 'Exporting...' : `Export as ${selectedFormat.toUpperCase()}`}
517
- </button>
518
-
519
- {exportError && (
520
- <p className="mt-2 text-sm text-red-600 dark:text-red-500">
521
- {exportError}
522
- </p>
523
- )}
524
- </div>
525
-
526
- {/* Export Tips */}
527
- <div className="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-md">
528
- <h3 className="text-sm font-bold text-blue-800 dark:text-blue-200 mb-2">Export Tips</h3>
529
- <ul className="text-blue-700 dark:text-blue-300 text-sm list-disc list-inside space-y-1">
530
- <li>PDF format is recommended for printing or sharing with stakeholders.</li>
531
- <li>HTML format is ideal for publishing on your website.</li>
532
- <li>DOCX format allows for further editing in Microsoft Word or similar applications.</li>
533
- <li>Markdown format is useful for version control systems or technical documentation.</li>
534
- </ul>
535
- </div>
536
-
537
- {/* Export History */}
538
- {renderExportHistory()}
539
- </div>
540
- );
541
- };