@tantainnovative/ndpr-toolkit 1.0.1 → 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 -431
- 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} +14 -1
- 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,541 @@
|
|
|
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>© ${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
|
+
};
|