@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,148 @@
1
+ import { RiskAssessmentQuestion } from '@/types';
2
+
3
+ export const dpiaQuestions: RiskAssessmentQuestion[] = [
4
+ {
5
+ id: 'data-collection-1',
6
+ text: 'Does your project involve collecting personal data directly from individuals?',
7
+ type: 'radio',
8
+ required: true,
9
+ options: [
10
+ { value: "1", label: 'No personal data is collected' },
11
+ { value: "2", label: 'Limited personal data is collected with clear consent' },
12
+ { value: "3", label: 'Significant personal data is collected with consent' },
13
+ { value: "4", label: 'Extensive personal data is collected' }
14
+ ]
15
+ },
16
+ {
17
+ id: 'data-collection-2',
18
+ text: 'Does your project involve collecting sensitive personal data (e.g., health, biometric, religious beliefs)?',
19
+ type: 'radio',
20
+ required: true,
21
+ options: [
22
+ { value: "1", label: 'No sensitive data is collected' },
23
+ { value: "2", label: 'Limited sensitive data with explicit consent' },
24
+ { value: "3", label: 'Significant sensitive data with explicit consent' },
25
+ { value: "4", label: 'Extensive sensitive data collection' }
26
+ ]
27
+ },
28
+ {
29
+ id: 'data-collection-3',
30
+ text: 'Does your project collect data from children or vulnerable individuals?',
31
+ type: 'radio',
32
+ required: true,
33
+ options: [
34
+ { value: "1", label: 'No data from children or vulnerable individuals' },
35
+ { value: "2", label: 'Limited data with parental/guardian consent' },
36
+ { value: "3", label: 'Significant data with enhanced safeguards' },
37
+ { value: "4", label: 'Extensive data from vulnerable groups' }
38
+ ]
39
+ },
40
+ {
41
+ id: 'data-processing-1',
42
+ text: 'Does your project involve automated decision-making or profiling?',
43
+ type: 'radio',
44
+ required: true,
45
+ options: [
46
+ { value: "1", label: 'No automated decision-making' },
47
+ { value: "2", label: 'Limited automation with human oversight' },
48
+ { value: "3", label: 'Significant automation with opt-out options' },
49
+ { value: "4", label: 'Extensive automated decisions affecting individuals' }
50
+ ]
51
+ },
52
+ {
53
+ id: 'data-processing-2',
54
+ text: 'Does your project involve processing data for purposes beyond what was initially collected for?',
55
+ type: 'radio',
56
+ required: true,
57
+ options: [
58
+ { value: "1", label: 'Processing only for original purpose' },
59
+ { value: "2", label: 'Limited secondary processing with notice' },
60
+ { value: "3", label: 'Significant secondary processing with consent' },
61
+ { value: "4", label: 'Extensive repurposing of data' }
62
+ ]
63
+ },
64
+ {
65
+ id: 'data-processing-3',
66
+ text: 'Does your project combine data from multiple sources?',
67
+ type: 'radio',
68
+ required: true,
69
+ options: [
70
+ { value: "1", label: 'No data combination' },
71
+ { value: "2", label: 'Limited combination from controlled sources' },
72
+ { value: "3", label: 'Significant combination with transparency' },
73
+ { value: "4", label: 'Extensive data aggregation from various sources' }
74
+ ]
75
+ },
76
+ {
77
+ id: 'data-sharing-1',
78
+ text: 'Does your project involve sharing personal data with third parties?',
79
+ type: 'radio',
80
+ required: true,
81
+ options: [
82
+ { value: "1", label: 'No third-party sharing' },
83
+ { value: "2", label: 'Limited sharing with contractual safeguards' },
84
+ { value: "3", label: 'Significant sharing with data processing agreements' },
85
+ { value: "4", label: 'Extensive sharing with multiple third parties' }
86
+ ]
87
+ },
88
+ {
89
+ id: 'data-sharing-2',
90
+ text: 'Does your project involve transferring data outside Nigeria?',
91
+ type: 'radio',
92
+ required: true,
93
+ options: [
94
+ { value: "1", label: 'No international transfers' },
95
+ { value: "2", label: 'Limited transfers to countries with adequate protection' },
96
+ { value: "3", label: 'Significant transfers with standard contractual clauses' },
97
+ { value: "4", label: 'Extensive transfers to countries without adequate protection' }
98
+ ]
99
+ },
100
+ {
101
+ id: 'security-1',
102
+ text: 'What level of security measures does your project implement?',
103
+ type: 'radio',
104
+ required: true,
105
+ options: [
106
+ { value: "1", label: 'Comprehensive security with encryption, access controls, and regular audits' },
107
+ { value: "2", label: 'Strong security measures with some monitoring' },
108
+ { value: "3", label: 'Basic security measures meeting minimum requirements' },
109
+ { value: "4", label: 'Limited security measures' }
110
+ ]
111
+ },
112
+ {
113
+ id: 'security-2',
114
+ text: 'Does your project have a data breach response plan?',
115
+ type: 'radio',
116
+ required: true,
117
+ options: [
118
+ { value: "1", label: 'Comprehensive breach plan with regular testing' },
119
+ { value: "2", label: 'Documented breach plan with assigned responsibilities' },
120
+ { value: "3", label: 'Basic breach notification procedure' },
121
+ { value: "4", label: 'No formal breach response plan' }
122
+ ]
123
+ },
124
+ {
125
+ id: 'data-subject-rights-1',
126
+ text: 'How does your project facilitate data subject rights (access, rectification, erasure, etc.)?',
127
+ type: 'radio',
128
+ required: true,
129
+ options: [
130
+ { value: "1", label: 'Automated self-service portal for all rights' },
131
+ { value: "2", label: 'Documented procedures with reasonable response times' },
132
+ { value: "3", label: 'Basic manual process for handling requests' },
133
+ { value: "4", label: 'Limited or no formal process for rights requests' }
134
+ ]
135
+ },
136
+ {
137
+ id: 'data-subject-rights-2',
138
+ text: 'Does your project provide clear privacy information to data subjects?',
139
+ type: 'radio',
140
+ required: true,
141
+ options: [
142
+ { value: "1", label: 'Comprehensive, layered privacy notices in plain language' },
143
+ { value: "2", label: 'Clear privacy policy with all required information' },
144
+ { value: "3", label: 'Basic privacy notice covering essential elements' },
145
+ { value: "4", label: 'Minimal or complex privacy information' }
146
+ ]
147
+ }
148
+ ];
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+
3
+ import { DataSubjectRequest, RequestStatus } from '@/types';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { storage } from './storage';
6
+
7
+ const REQUEST_STORAGE_KEY = 'ndpr_requests';
8
+
9
+ const getStoredRequests = (): DataSubjectRequest[] => {
10
+ return storage.getItem<DataSubjectRequest[]>(REQUEST_STORAGE_KEY, []) || [];
11
+ };
12
+
13
+ export const requestService = {
14
+ createRequest: (
15
+ requestType: DataSubjectRequest['type'],
16
+ requesterName: string,
17
+ requesterEmail: string,
18
+ details: string,
19
+ consent: boolean
20
+ ): DataSubjectRequest => {
21
+ const request: DataSubjectRequest = {
22
+ id: uuidv4(),
23
+ type: requestType,
24
+ status: 'pending',
25
+ createdAt: Date.now(),
26
+ updatedAt: Date.now(),
27
+ subject: {
28
+ name: requesterName,
29
+ email: requesterEmail,
30
+ },
31
+ description: details,
32
+ };
33
+
34
+ const requests = getStoredRequests();
35
+ requests.push(request);
36
+ const saved = storage.setItem(REQUEST_STORAGE_KEY, requests);
37
+
38
+ if (!saved) {
39
+ throw new Error('Failed to save request. Storage may be full or unavailable.');
40
+ }
41
+
42
+ return request;
43
+ },
44
+
45
+ updateStatus: (id: string, status: RequestStatus): DataSubjectRequest | null => {
46
+ const requests = getStoredRequests();
47
+ const idx = requests.findIndex(r => r.id === id);
48
+ if (idx === -1) return null;
49
+
50
+ requests[idx].status = status;
51
+ requests[idx].updatedAt = Date.now();
52
+
53
+ const saved = storage.setItem(REQUEST_STORAGE_KEY, requests);
54
+ if (!saved) {
55
+ throw new Error('Failed to update request status.');
56
+ }
57
+
58
+ return requests[idx];
59
+ },
60
+
61
+ getRequest: (id: string): DataSubjectRequest | null => {
62
+ const requests = getStoredRequests();
63
+ return requests.find(r => r.id === id) || null;
64
+ },
65
+
66
+ getAllRequests: (): DataSubjectRequest[] => {
67
+ return getStoredRequests();
68
+ },
69
+
70
+ clear: (): boolean => {
71
+ return storage.removeItem(REQUEST_STORAGE_KEY);
72
+ },
73
+ };
74
+
75
+ export default requestService;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Sanitization utilities for preventing XSS attacks
3
+ * In production, consider using a library like DOMPurify
4
+ */
5
+
6
+ /**
7
+ * Escapes HTML special characters to prevent XSS
8
+ */
9
+ export function escapeHtml(unsafe: string): string {
10
+ return unsafe
11
+ .replace(/&/g, "&amp;")
12
+ .replace(/</g, "&lt;")
13
+ .replace(/>/g, "&gt;")
14
+ .replace(/"/g, "&quot;")
15
+ .replace(/'/g, "&#039;");
16
+ }
17
+
18
+ /**
19
+ * Sanitizes user input for safe display
20
+ * Removes dangerous tags and attributes
21
+ */
22
+ export function sanitizeInput(input: string): string {
23
+ // Remove script tags and their content
24
+ let sanitized = input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
25
+
26
+ // Remove on* event handlers
27
+ sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, '');
28
+
29
+ // Remove javascript: protocol
30
+ sanitized = sanitized.replace(/javascript:/gi, '');
31
+
32
+ // Escape remaining HTML
33
+ return escapeHtml(sanitized);
34
+ }
35
+
36
+ /**
37
+ * Validates and sanitizes email addresses
38
+ */
39
+ export function sanitizeEmail(email: string): string {
40
+ // Basic email validation
41
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
42
+
43
+ if (!emailRegex.test(email)) {
44
+ throw new Error('Invalid email format');
45
+ }
46
+
47
+ // Remove any HTML tags
48
+ return email.replace(/<[^>]*>/g, '').trim().toLowerCase();
49
+ }
50
+
51
+ /**
52
+ * Sanitizes file names to prevent directory traversal attacks
53
+ */
54
+ export function sanitizeFileName(fileName: string): string {
55
+ // Remove path separators and null bytes
56
+ return fileName
57
+ .replace(/[\/\\]/g, '')
58
+ .replace(/\0/g, '')
59
+ .replace(/\.{2,}/g, '.')
60
+ .trim();
61
+ }
62
+
63
+ /**
64
+ * Creates a safe HTML structure from markdown
65
+ * This is a basic implementation - for production use a proper markdown parser
66
+ */
67
+ export function markdownToSafeHtml(markdown: string): string {
68
+ let html = escapeHtml(markdown);
69
+
70
+ // Convert markdown syntax to HTML
71
+ html = html
72
+ // Headers
73
+ .replace(/^### (.*$)/gim, '<h3>$1</h3>')
74
+ .replace(/^## (.*$)/gim, '<h2>$1</h2>')
75
+ .replace(/^# (.*$)/gim, '<h1>$1</h1>')
76
+ // Bold
77
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
78
+ // Italic
79
+ .replace(/\*([^*]+)\*/g, '<em>$1</em>')
80
+ // Line breaks
81
+ .replace(/\n/g, '<br />');
82
+
83
+ return html;
84
+ }
85
+
86
+ /**
87
+ * Validates and sanitizes URL to prevent open redirect vulnerabilities
88
+ */
89
+ export function sanitizeUrl(url: string, allowedHosts?: string[]): string {
90
+ try {
91
+ const parsed = new URL(url);
92
+
93
+ // Check if protocol is safe
94
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
95
+ throw new Error('Invalid protocol');
96
+ }
97
+
98
+ // Check if host is allowed
99
+ if (allowedHosts && !allowedHosts.includes(parsed.hostname)) {
100
+ throw new Error('Host not allowed');
101
+ }
102
+
103
+ return parsed.toString();
104
+ } catch {
105
+ // If URL parsing fails, return a safe default
106
+ return '#';
107
+ }
108
+ }
@@ -0,0 +1,222 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Safe localStorage wrapper with error handling
5
+ */
6
+ export class SafeStorage {
7
+ private static isStorageAvailable(): boolean {
8
+ if (typeof window === "undefined") return false;
9
+
10
+ try {
11
+ const testKey = "__storage_test__";
12
+ window.localStorage.setItem(testKey, "test");
13
+ window.localStorage.removeItem(testKey);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ static getItem<T = unknown>(key: string, defaultValue?: T): T | null {
21
+ if (!this.isStorageAvailable()) {
22
+ console.warn("localStorage is not available");
23
+ return defaultValue ?? null;
24
+ }
25
+
26
+ try {
27
+ const item = window.localStorage.getItem(key);
28
+ if (item === null) return defaultValue ?? null;
29
+
30
+ // Try to parse as JSON, otherwise return as string
31
+ try {
32
+ return JSON.parse(item) as T;
33
+ } catch {
34
+ return item as unknown as T;
35
+ }
36
+ } catch (error) {
37
+ console.error(`Error reading from localStorage: ${key}`, error);
38
+ return defaultValue ?? null;
39
+ }
40
+ }
41
+
42
+ static setItem(key: string, value: unknown): boolean {
43
+ if (!this.isStorageAvailable()) {
44
+ console.warn("localStorage is not available");
45
+ return false;
46
+ }
47
+
48
+ try {
49
+ const serialized =
50
+ typeof value === "string" ? value : JSON.stringify(value);
51
+ window.localStorage.setItem(key, serialized);
52
+ return true;
53
+ } catch (error) {
54
+ if (error instanceof Error) {
55
+ if (error.name === "QuotaExceededError") {
56
+ console.error("localStorage quota exceeded");
57
+ // Try to clear old data
58
+ this.clearOldData();
59
+ // Retry once
60
+ try {
61
+ const serialized =
62
+ typeof value === "string" ? value : JSON.stringify(value);
63
+ window.localStorage.setItem(key, serialized);
64
+ return true;
65
+ } catch {
66
+ console.error("Failed to save after clearing old data");
67
+ return false;
68
+ }
69
+ }
70
+ }
71
+ console.error(`Error writing to localStorage: ${key}`, error);
72
+ return false;
73
+ }
74
+ }
75
+
76
+ static removeItem(key: string): boolean {
77
+ if (!this.isStorageAvailable()) {
78
+ console.warn("localStorage is not available");
79
+ return false;
80
+ }
81
+
82
+ try {
83
+ window.localStorage.removeItem(key);
84
+ return true;
85
+ } catch (error) {
86
+ console.error(`Error removing from localStorage: ${key}`, error);
87
+ return false;
88
+ }
89
+ }
90
+
91
+ static clear(): boolean {
92
+ if (!this.isStorageAvailable()) {
93
+ console.warn("localStorage is not available");
94
+ return false;
95
+ }
96
+
97
+ try {
98
+ window.localStorage.clear();
99
+ return true;
100
+ } catch (error) {
101
+ console.error("Error clearing localStorage", error);
102
+ return false;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Get storage size in bytes
108
+ */
109
+ static getStorageSize(): number {
110
+ if (!this.isStorageAvailable()) return 0;
111
+
112
+ let size = 0;
113
+ try {
114
+ for (const key in window.localStorage) {
115
+ if (window.localStorage.hasOwnProperty(key)) {
116
+ size += window.localStorage[key].length + key.length;
117
+ }
118
+ }
119
+ } catch (error) {
120
+ console.error("Error calculating storage size", error);
121
+ }
122
+ return size;
123
+ }
124
+
125
+ /**
126
+ * Clear old data based on timestamp
127
+ * Removes items older than specified days
128
+ */
129
+ static clearOldData(daysOld: number = 30): void {
130
+ if (!this.isStorageAvailable()) return;
131
+
132
+ const cutoffTime = Date.now() - daysOld * 24 * 60 * 60 * 1000;
133
+ const keysToRemove: string[] = [];
134
+
135
+ try {
136
+ for (const key in window.localStorage) {
137
+ if (window.localStorage.hasOwnProperty(key)) {
138
+ try {
139
+ const item = window.localStorage.getItem(key);
140
+ if (item) {
141
+ const parsed = JSON.parse(item);
142
+ if (parsed.timestamp && parsed.timestamp < cutoffTime) {
143
+ keysToRemove.push(key);
144
+ }
145
+ }
146
+ } catch {
147
+ // If parsing fails, skip this item
148
+ }
149
+ }
150
+ }
151
+
152
+ // Remove old items
153
+ keysToRemove.forEach((key) => this.removeItem(key));
154
+ } catch (error) {
155
+ console.error("Error clearing old data", error);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Wrapper for data with timestamp
161
+ */
162
+ static setItemWithTimestamp(key: string, value: unknown): boolean {
163
+ const wrappedData = {
164
+ data: value,
165
+ timestamp: Date.now(),
166
+ };
167
+ return this.setItem(key, wrappedData);
168
+ }
169
+
170
+ /**
171
+ * Get item with timestamp check
172
+ */
173
+ static getItemWithTimestamp<T = unknown>(
174
+ key: string,
175
+ maxAge?: number,
176
+ ): T | null {
177
+ const wrapped = this.getItem<{ data: T; timestamp: number }>(key);
178
+ if (!wrapped) return null;
179
+
180
+ if (maxAge && wrapped.timestamp) {
181
+ const age = Date.now() - wrapped.timestamp;
182
+ if (age > maxAge) {
183
+ this.removeItem(key);
184
+ return null;
185
+ }
186
+ }
187
+
188
+ return wrapped.data;
189
+ }
190
+ }
191
+
192
+ // Retry decorator for storage operations
193
+ export function withRetry<T extends (...args: unknown[]) => unknown>(
194
+ fn: T,
195
+ maxRetries: number = 3,
196
+ delay: number = 100,
197
+ ): T {
198
+ return ((...args: Parameters<T>) => {
199
+ let lastError: Error | null = null;
200
+
201
+ for (let i = 0; i < maxRetries; i++) {
202
+ try {
203
+ return fn(...args);
204
+ } catch (error) {
205
+ lastError = error as Error;
206
+ if (i < maxRetries - 1) {
207
+ // Wait before retrying
208
+ const waitTime = delay * Math.pow(2, i); // Exponential backoff
209
+ const start = Date.now();
210
+ while (Date.now() - start < waitTime) {
211
+ // Busy wait
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ throw lastError;
218
+ }) as T;
219
+ }
220
+
221
+ // Export a default instance
222
+ export const storage = SafeStorage;
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,30 @@
1
+ declare module 'html-to-docx' {
2
+ export default function htmlToDocx(
3
+ htmlString: string,
4
+ options?: {
5
+ title?: string;
6
+ margin?: {
7
+ top?: number;
8
+ right?: number;
9
+ bottom?: number;
10
+ left?: number;
11
+ };
12
+ header?: {
13
+ default?: string;
14
+ first?: string;
15
+ even?: string;
16
+ };
17
+ footer?: {
18
+ default?: string;
19
+ first?: string;
20
+ even?: string;
21
+ };
22
+ pageNumber?: boolean;
23
+ orientation?: 'portrait' | 'landscape';
24
+ font?: {
25
+ family?: string;
26
+ size?: number;
27
+ };
28
+ }
29
+ ): Promise<Buffer>;
30
+ }
@@ -0,0 +1,72 @@
1
+ // Re-export all types from the package
2
+ // This ensures consistency between the main app and the package
3
+ export * from '@tantainnovative/ndpr-toolkit';
4
+ import type { DSRStatus, DSRType } from '@tantainnovative/ndpr-toolkit';
5
+
6
+ // Additional app-specific types that extend the package types
7
+ export interface AppConfig {
8
+ apiUrl?: string;
9
+ environment: 'development' | 'staging' | 'production';
10
+ features: {
11
+ consent: boolean;
12
+ dsr: boolean;
13
+ dpia: boolean;
14
+ breach: boolean;
15
+ privacy: boolean;
16
+ };
17
+ }
18
+
19
+ // Legacy type aliases for backward compatibility
20
+ // These map old type names to new ones from the package
21
+ export type RequestStatus = DSRStatus;
22
+ export type RequestType = DSRType;
23
+
24
+ // Re-export specific types that might have different names
25
+ export type {
26
+ ConsentOption,
27
+ BreachReport,
28
+ RiskAssessment,
29
+ NotificationRequirement,
30
+ RegulatoryNotification,
31
+ DSRRequest as DataSubjectRequest,
32
+ DSRStatus,
33
+ DSRType,
34
+ PrivacyPolicy,
35
+ PolicySection,
36
+ PolicyTemplate,
37
+ PolicyVariable,
38
+ OrganizationInfo,
39
+ DPIAQuestion as RiskAssessmentQuestion,
40
+ DPIASection,
41
+ DPIAResult
42
+ } from '@tantainnovative/ndpr-toolkit';
43
+
44
+ // Define ConsentType locally since it's not exported from the package
45
+ export type ConsentType = 'necessary' | 'functional' | 'analytics' | 'marketing' | 'preferences';
46
+
47
+ // Define BreachSeverity type
48
+ export type BreachSeverity = 'low' | 'medium' | 'high' | 'critical';
49
+
50
+ // Define missing types that are not exported from the package
51
+ export interface ConsentRecord {
52
+ id: string;
53
+ userId?: string;
54
+ consents: Record<string, boolean>;
55
+ timestamp: Date;
56
+ ipAddress?: string;
57
+ userAgent?: string;
58
+ version: string;
59
+ }
60
+
61
+ export interface ConsentHistoryEntry {
62
+ timestamp: Date;
63
+ consents: Record<string, boolean>;
64
+ action: 'granted' | 'revoked' | 'updated';
65
+ ipAddress?: string;
66
+ userAgent?: string;
67
+ version: string;
68
+ }
69
+
70
+ export interface ConsentPreferences {
71
+ [key: string]: boolean;
72
+ }