@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.
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 -431
  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} +14 -1
  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,569 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { BreachReport, RiskAssessment } from '../../types/breach';
3
+ import { calculateBreachSeverity } from '../../utils/breach';
4
+
5
+ export interface BreachRiskAssessmentProps {
6
+ /**
7
+ * The breach data to assess
8
+ */
9
+ breachData: BreachReport;
10
+
11
+ /**
12
+ * Initial assessment data (if editing an existing assessment)
13
+ */
14
+ initialAssessment?: Partial<RiskAssessment>;
15
+
16
+ /**
17
+ * Callback function called when assessment is completed
18
+ */
19
+ onComplete: (assessment: RiskAssessment) => void;
20
+
21
+ /**
22
+ * Title displayed on the assessment form
23
+ * @default "Breach Risk Assessment"
24
+ */
25
+ title?: string;
26
+
27
+ /**
28
+ * Description text displayed on the assessment form
29
+ * @default "Assess the risk level of this data breach to determine notification requirements."
30
+ */
31
+ description?: string;
32
+
33
+ /**
34
+ * Text for the submit button
35
+ * @default "Complete Assessment"
36
+ */
37
+ submitButtonText?: string;
38
+
39
+ /**
40
+ * Custom CSS class for the form
41
+ */
42
+ className?: string;
43
+
44
+ /**
45
+ * Custom CSS class for the submit button
46
+ */
47
+ buttonClassName?: string;
48
+
49
+ /**
50
+ * Whether to show the breach summary
51
+ * @default true
52
+ */
53
+ showBreachSummary?: boolean;
54
+
55
+ /**
56
+ * Whether to show notification requirements after assessment
57
+ * @default true
58
+ */
59
+ showNotificationRequirements?: boolean;
60
+ }
61
+
62
+ export const BreachRiskAssessment: React.FC<BreachRiskAssessmentProps> = ({
63
+ breachData,
64
+ initialAssessment = {},
65
+ onComplete,
66
+ title = "Breach Risk Assessment",
67
+ description = "Assess the risk level of this data breach to determine notification requirements.",
68
+ submitButtonText = "Complete Assessment",
69
+ className = "",
70
+ buttonClassName = "",
71
+ showBreachSummary = true,
72
+ showNotificationRequirements = true
73
+ }) => {
74
+ // Assessment form state
75
+ const [confidentialityImpact, setConfidentialityImpact] = useState<number>(initialAssessment.confidentialityImpact || 3);
76
+ const [integrityImpact, setIntegrityImpact] = useState<number>(initialAssessment.integrityImpact || 3);
77
+ const [availabilityImpact, setAvailabilityImpact] = useState<number>(initialAssessment.availabilityImpact || 3);
78
+ const [harmLikelihood, setHarmLikelihood] = useState<number>(initialAssessment.harmLikelihood || 3);
79
+ const [harmSeverity, setHarmSeverity] = useState<number>(initialAssessment.harmSeverity || 3);
80
+ const [risksToRightsAndFreedoms, setRisksToRightsAndFreedoms] = useState<boolean>(initialAssessment.risksToRightsAndFreedoms !== undefined ? initialAssessment.risksToRightsAndFreedoms : false);
81
+ const [highRisksToRightsAndFreedoms, setHighRisksToRightsAndFreedoms] = useState<boolean>(initialAssessment.highRisksToRightsAndFreedoms !== undefined ? initialAssessment.highRisksToRightsAndFreedoms : false);
82
+ const [justification, setJustification] = useState<string>(initialAssessment.justification || '');
83
+
84
+ // Calculated values
85
+ const [overallRiskScore, setOverallRiskScore] = useState<number>(0);
86
+ const [riskLevel, setRiskLevel] = useState<'low' | 'medium' | 'high' | 'critical'>('low');
87
+ const [notificationRequired, setNotificationRequired] = useState<boolean>(false);
88
+ const [notificationDeadline, setNotificationDeadline] = useState<number>(0);
89
+ const [hoursRemaining, setHoursRemaining] = useState<number>(0);
90
+ const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
91
+
92
+ // Calculate risk score and level when inputs change
93
+ useEffect(() => {
94
+ // Calculate overall risk score (weighted average)
95
+ const ciWeight = 0.2; // Confidentiality impact weight
96
+ const iiWeight = 0.1; // Integrity impact weight
97
+ const aiWeight = 0.1; // Availability impact weight
98
+ const hlWeight = 0.3; // Harm likelihood weight
99
+ const hsWeight = 0.3; // Harm severity weight
100
+
101
+ const score =
102
+ (confidentialityImpact * ciWeight) +
103
+ (integrityImpact * iiWeight) +
104
+ (availabilityImpact * aiWeight) +
105
+ (harmLikelihood * hlWeight) +
106
+ (harmSeverity * hsWeight);
107
+
108
+ setOverallRiskScore(Number(score.toFixed(1)));
109
+
110
+ // Determine risk level based on score
111
+ let level: 'low' | 'medium' | 'high' | 'critical';
112
+ if (score < 2) {
113
+ level = 'low';
114
+ } else if (score < 3) {
115
+ level = 'medium';
116
+ } else if (score < 4) {
117
+ level = 'high';
118
+ } else {
119
+ level = 'critical';
120
+ }
121
+ setRiskLevel(level);
122
+
123
+ // Determine notification requirements
124
+ const requiresNotification = risksToRightsAndFreedoms || level === 'high' || level === 'critical';
125
+ setNotificationRequired(requiresNotification);
126
+
127
+ // Calculate notification deadline (72 hours from discovery under NDPR)
128
+ const deadline = breachData.discoveredAt + (72 * 60 * 60 * 1000);
129
+ setNotificationDeadline(deadline);
130
+
131
+ // Calculate hours remaining
132
+ const now = Date.now();
133
+ const remaining = (deadline - now) / (60 * 60 * 1000);
134
+ setHoursRemaining(Number(remaining.toFixed(1)));
135
+ }, [
136
+ confidentialityImpact,
137
+ integrityImpact,
138
+ availabilityImpact,
139
+ harmLikelihood,
140
+ harmSeverity,
141
+ risksToRightsAndFreedoms,
142
+ breachData.discoveredAt
143
+ ]);
144
+
145
+ // Format a date from timestamp
146
+ const formatDate = (timestamp: number): string => {
147
+ return new Date(timestamp).toLocaleString();
148
+ };
149
+
150
+ // Handle form submission
151
+ const handleSubmit = (e: React.FormEvent) => {
152
+ e.preventDefault();
153
+
154
+ const assessment: RiskAssessment = {
155
+ id: initialAssessment.id || `assessment_${Date.now()}`,
156
+ breachId: breachData.id,
157
+ assessedAt: Date.now(),
158
+ assessor: initialAssessment.assessor || {
159
+ name: 'Assessment User', // This would typically come from the user context
160
+ role: 'Data Protection Officer',
161
+ email: 'dpo@example.com'
162
+ },
163
+ confidentialityImpact,
164
+ integrityImpact,
165
+ availabilityImpact,
166
+ harmLikelihood,
167
+ harmSeverity,
168
+ overallRiskScore,
169
+ riskLevel,
170
+ risksToRightsAndFreedoms,
171
+ highRisksToRightsAndFreedoms,
172
+ justification
173
+ };
174
+
175
+ onComplete(assessment);
176
+ setIsSubmitted(true);
177
+ };
178
+
179
+ // Render impact level description
180
+ const renderImpactDescription = (level: number): string => {
181
+ switch (level) {
182
+ case 1: return 'Minimal';
183
+ case 2: return 'Low';
184
+ case 3: return 'Moderate';
185
+ case 4: return 'High';
186
+ case 5: return 'Severe';
187
+ default: return 'Unknown';
188
+ }
189
+ };
190
+
191
+ // Render risk level badge
192
+ const renderRiskLevelBadge = (level: 'low' | 'medium' | 'high' | 'critical') => {
193
+ const colorClasses = {
194
+ low: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
195
+ medium: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
196
+ high: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200',
197
+ critical: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
198
+ };
199
+
200
+ return (
201
+ <span className={`px-2 py-1 rounded text-xs font-medium ${colorClasses[level]}`}>
202
+ {level.charAt(0).toUpperCase() + level.slice(1)}
203
+ </span>
204
+ );
205
+ };
206
+
207
+ return (
208
+ <div className={`bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md ${className}`}>
209
+ <h2 className="text-xl font-bold mb-2">{title}</h2>
210
+ <p className="mb-6 text-gray-600 dark:text-gray-300">{description}</p>
211
+
212
+ {/* Breach Summary */}
213
+ {showBreachSummary && (
214
+ <div className="mb-6 p-4 bg-gray-50 dark:bg-gray-700 rounded-md">
215
+ <h3 className="text-lg font-medium mb-2">Breach Summary</h3>
216
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
217
+ <div>
218
+ <p className="text-sm"><span className="font-medium">Title:</span> {breachData.title}</p>
219
+ <p className="text-sm"><span className="font-medium">Discovered:</span> {formatDate(breachData.discoveredAt)}</p>
220
+ <p className="text-sm"><span className="font-medium">Status:</span> {breachData.status.charAt(0).toUpperCase() + breachData.status.slice(1)}</p>
221
+ </div>
222
+ <div>
223
+ <p className="text-sm"><span className="font-medium">Data Types:</span> {breachData.dataTypes.join(', ')}</p>
224
+ <p className="text-sm"><span className="font-medium">Affected Systems:</span> {breachData.affectedSystems.join(', ')}</p>
225
+ <p className="text-sm"><span className="font-medium">Affected Subjects:</span> {breachData.estimatedAffectedSubjects || 'Unknown'}</p>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ )}
230
+
231
+ {isSubmitted ? (
232
+ <div>
233
+ {/* Assessment Results */}
234
+ <div className="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-md">
235
+ <h3 className="text-lg font-medium mb-3">Assessment Results</h3>
236
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
237
+ <div>
238
+ <p className="text-sm mb-2">
239
+ <span className="font-medium">Overall Risk Level:</span>{' '}
240
+ {renderRiskLevelBadge(riskLevel)}
241
+ </p>
242
+ <p className="text-sm mb-2">
243
+ <span className="font-medium">Risk Score:</span> {overallRiskScore} / 5
244
+ </p>
245
+ <p className="text-sm">
246
+ <span className="font-medium">Assessed On:</span> {formatDate(Date.now())}
247
+ </p>
248
+ </div>
249
+ <div>
250
+ <p className="text-sm mb-2">
251
+ <span className="font-medium">Risks to Rights and Freedoms:</span>{' '}
252
+ {risksToRightsAndFreedoms ? 'Yes' : 'No'}
253
+ </p>
254
+ <p className="text-sm mb-2">
255
+ <span className="font-medium">High Risks to Rights and Freedoms:</span>{' '}
256
+ {highRisksToRightsAndFreedoms ? 'Yes' : 'No'}
257
+ </p>
258
+ </div>
259
+ </div>
260
+ <div className="mt-3">
261
+ <p className="text-sm mb-1"><span className="font-medium">Justification:</span></p>
262
+ <p className="text-sm bg-white dark:bg-gray-800 p-2 rounded">{justification}</p>
263
+ </div>
264
+ </div>
265
+
266
+ {/* Notification Requirements */}
267
+ {showNotificationRequirements && (
268
+ <div className={`mb-6 p-4 rounded-md ${
269
+ notificationRequired
270
+ ? hoursRemaining > 24
271
+ ? 'bg-yellow-50 dark:bg-yellow-900/20'
272
+ : 'bg-red-50 dark:bg-red-900/20'
273
+ : 'bg-green-50 dark:bg-green-900/20'
274
+ }`}>
275
+ <h3 className="text-lg font-medium mb-3">Notification Requirements</h3>
276
+
277
+ {notificationRequired ? (
278
+ <div>
279
+ <p className={`text-sm font-bold mb-2 ${
280
+ hoursRemaining > 24
281
+ ? 'text-yellow-800 dark:text-yellow-200'
282
+ : 'text-red-800 dark:text-red-200'
283
+ }`}>
284
+ NITDA Notification Required
285
+ </p>
286
+ <p className="text-sm mb-2">
287
+ Under the NDPR, this breach must be reported to NITDA within 72 hours of discovery.
288
+ </p>
289
+ <p className="text-sm mb-2">
290
+ <span className="font-medium">Notification Deadline:</span> {formatDate(notificationDeadline)}
291
+ </p>
292
+ <p className="text-sm mb-2">
293
+ <span className="font-medium">Time Remaining:</span>{' '}
294
+ <span className={hoursRemaining < 24 ? 'text-red-600 dark:text-red-400 font-bold' : ''}>
295
+ {hoursRemaining > 0 ? `${hoursRemaining} hours` : 'Deadline passed'}
296
+ </span>
297
+ </p>
298
+ <p className="text-sm mb-2">
299
+ <span className="font-medium">Data Subject Notification:</span>{' '}
300
+ {highRisksToRightsAndFreedoms ? 'Required' : 'Not required unless directed by NITDA'}
301
+ </p>
302
+ </div>
303
+ ) : (
304
+ <div>
305
+ <p className="text-sm font-bold mb-2 text-green-800 dark:text-green-200">
306
+ NITDA Notification Not Required
307
+ </p>
308
+ <p className="text-sm mb-2">
309
+ Based on this assessment, this breach does not need to be reported to NITDA.
310
+ </p>
311
+ <p className="text-sm mb-2">
312
+ However, the breach should still be documented internally for compliance purposes.
313
+ </p>
314
+ </div>
315
+ )}
316
+
317
+ <div className="mt-3 text-sm">
318
+ <p className="font-medium">Next Steps:</p>
319
+ <ul className="list-disc pl-5 mt-1">
320
+ {notificationRequired ? (
321
+ <>
322
+ <li>Prepare a notification report for NITDA</li>
323
+ <li>Document all aspects of the breach and your response</li>
324
+ {highRisksToRightsAndFreedoms && <li>Prepare communications for affected data subjects</li>}
325
+ <li>Implement measures to mitigate the impact of the breach</li>
326
+ </>
327
+ ) : (
328
+ <>
329
+ <li>Document the breach and this assessment in your internal records</li>
330
+ <li>Implement measures to prevent similar breaches in the future</li>
331
+ <li>Review and update security measures as needed</li>
332
+ </>
333
+ )}
334
+ </ul>
335
+ </div>
336
+ </div>
337
+ )}
338
+
339
+ <button
340
+ onClick={() => setIsSubmitted(false)}
341
+ className={`px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 ${buttonClassName}`}
342
+ >
343
+ Edit Assessment
344
+ </button>
345
+ </div>
346
+ ) : (
347
+ <form onSubmit={handleSubmit}>
348
+ <div className="space-y-6">
349
+ {/* Impact Assessment */}
350
+ <div>
351
+ <h3 className="text-lg font-semibold mb-3">Impact Assessment</h3>
352
+
353
+ <div className="mb-4">
354
+ <label htmlFor="confidentialityImpact" className="block text-sm font-medium mb-1">
355
+ Confidentiality Impact (1-5)
356
+ <span className="ml-2 text-sm text-gray-500 dark:text-gray-400">
357
+ How much has the confidentiality of data been compromised?
358
+ </span>
359
+ </label>
360
+ <div className="flex items-center">
361
+ <input
362
+ type="range"
363
+ id="confidentialityImpact"
364
+ min="1"
365
+ max="5"
366
+ step="1"
367
+ value={confidentialityImpact}
368
+ onChange={e => setConfidentialityImpact(parseInt(e.target.value))}
369
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
370
+ />
371
+ <span className="ml-3 w-24 text-sm">
372
+ {renderImpactDescription(confidentialityImpact)} ({confidentialityImpact})
373
+ </span>
374
+ </div>
375
+ </div>
376
+
377
+ <div className="mb-4">
378
+ <label htmlFor="integrityImpact" className="block text-sm font-medium mb-1">
379
+ Integrity Impact (1-5)
380
+ <span className="ml-2 text-sm text-gray-500 dark:text-gray-400">
381
+ How much has the integrity of data been compromised?
382
+ </span>
383
+ </label>
384
+ <div className="flex items-center">
385
+ <input
386
+ type="range"
387
+ id="integrityImpact"
388
+ min="1"
389
+ max="5"
390
+ step="1"
391
+ value={integrityImpact}
392
+ onChange={e => setIntegrityImpact(parseInt(e.target.value))}
393
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
394
+ />
395
+ <span className="ml-3 w-24 text-sm">
396
+ {renderImpactDescription(integrityImpact)} ({integrityImpact})
397
+ </span>
398
+ </div>
399
+ </div>
400
+
401
+ <div className="mb-4">
402
+ <label htmlFor="availabilityImpact" className="block text-sm font-medium mb-1">
403
+ Availability Impact (1-5)
404
+ <span className="ml-2 text-sm text-gray-500 dark:text-gray-400">
405
+ How much has the availability of data or systems been compromised?
406
+ </span>
407
+ </label>
408
+ <div className="flex items-center">
409
+ <input
410
+ type="range"
411
+ id="availabilityImpact"
412
+ min="1"
413
+ max="5"
414
+ step="1"
415
+ value={availabilityImpact}
416
+ onChange={e => setAvailabilityImpact(parseInt(e.target.value))}
417
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
418
+ />
419
+ <span className="ml-3 w-24 text-sm">
420
+ {renderImpactDescription(availabilityImpact)} ({availabilityImpact})
421
+ </span>
422
+ </div>
423
+ </div>
424
+ </div>
425
+
426
+ {/* Risk to Data Subjects */}
427
+ <div>
428
+ <h3 className="text-lg font-semibold mb-3">Risk to Data Subjects</h3>
429
+
430
+ <div className="mb-4">
431
+ <label htmlFor="harmLikelihood" className="block text-sm font-medium mb-1">
432
+ Likelihood of Harm (1-5)
433
+ <span className="ml-2 text-sm text-gray-500 dark:text-gray-400">
434
+ How likely is it that data subjects will experience harm?
435
+ </span>
436
+ </label>
437
+ <div className="flex items-center">
438
+ <input
439
+ type="range"
440
+ id="harmLikelihood"
441
+ min="1"
442
+ max="5"
443
+ step="1"
444
+ value={harmLikelihood}
445
+ onChange={e => setHarmLikelihood(parseInt(e.target.value))}
446
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
447
+ />
448
+ <span className="ml-3 w-24 text-sm">
449
+ {renderImpactDescription(harmLikelihood)} ({harmLikelihood})
450
+ </span>
451
+ </div>
452
+ </div>
453
+
454
+ <div className="mb-4">
455
+ <label htmlFor="harmSeverity" className="block text-sm font-medium mb-1">
456
+ Severity of Harm (1-5)
457
+ <span className="ml-2 text-sm text-gray-500 dark:text-gray-400">
458
+ How severe would the harm be to affected data subjects?
459
+ </span>
460
+ </label>
461
+ <div className="flex items-center">
462
+ <input
463
+ type="range"
464
+ id="harmSeverity"
465
+ min="1"
466
+ max="5"
467
+ step="1"
468
+ value={harmSeverity}
469
+ onChange={e => setHarmSeverity(parseInt(e.target.value))}
470
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
471
+ />
472
+ <span className="ml-3 w-24 text-sm">
473
+ {renderImpactDescription(harmSeverity)} ({harmSeverity})
474
+ </span>
475
+ </div>
476
+ </div>
477
+
478
+ <div className="mb-4">
479
+ <div className="flex items-center mb-2">
480
+ <input
481
+ type="checkbox"
482
+ id="risksToRightsAndFreedoms"
483
+ checked={risksToRightsAndFreedoms}
484
+ onChange={e => setRisksToRightsAndFreedoms(e.target.checked)}
485
+ className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
486
+ />
487
+ <label htmlFor="risksToRightsAndFreedoms" className="ml-2 text-sm font-medium">
488
+ This breach poses a risk to the rights and freedoms of data subjects
489
+ </label>
490
+ </div>
491
+ <p className="text-xs text-gray-500 dark:text-gray-400 ml-6">
492
+ Under the NDPR, breaches that pose a risk to rights and freedoms must be reported to NITDA within 72 hours.
493
+ </p>
494
+ </div>
495
+
496
+ <div className="mb-4">
497
+ <div className="flex items-center mb-2">
498
+ <input
499
+ type="checkbox"
500
+ id="highRisksToRightsAndFreedoms"
501
+ checked={highRisksToRightsAndFreedoms}
502
+ onChange={e => setHighRisksToRightsAndFreedoms(e.target.checked)}
503
+ className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
504
+ />
505
+ <label htmlFor="highRisksToRightsAndFreedoms" className="ml-2 text-sm font-medium">
506
+ This breach poses a high risk to the rights and freedoms of data subjects
507
+ </label>
508
+ </div>
509
+ <p className="text-xs text-gray-500 dark:text-gray-400 ml-6">
510
+ Under the NDPR, breaches that pose a high risk to rights and freedoms also require notification to affected data subjects.
511
+ </p>
512
+ </div>
513
+ </div>
514
+
515
+ {/* Overall Assessment */}
516
+ <div>
517
+ <h3 className="text-lg font-semibold mb-3">Overall Assessment</h3>
518
+
519
+ <div className="mb-4 p-4 bg-gray-50 dark:bg-gray-700 rounded-md">
520
+ <div className="flex items-center justify-between mb-2">
521
+ <span className="font-medium">Overall Risk Score:</span>
522
+ <span>{overallRiskScore} / 5</span>
523
+ </div>
524
+ <div className="flex items-center justify-between">
525
+ <span className="font-medium">Risk Level:</span>
526
+ {renderRiskLevelBadge(riskLevel)}
527
+ </div>
528
+ </div>
529
+
530
+ <div className="mb-4">
531
+ <label htmlFor="justification" className="block text-sm font-medium mb-1">
532
+ Justification for Assessment <span className="text-red-500">*</span>
533
+ </label>
534
+ <textarea
535
+ id="justification"
536
+ value={justification}
537
+ onChange={e => setJustification(e.target.value)}
538
+ rows={4}
539
+ placeholder="Explain the reasoning behind your assessment, including any factors that influenced your decision."
540
+ 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"
541
+ required
542
+ />
543
+ </div>
544
+ </div>
545
+
546
+ {/* NDPR Notice */}
547
+ <div className="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-md">
548
+ <h3 className="text-sm font-bold text-blue-800 dark:text-blue-200 mb-2">NDPR Breach Notification Requirements</h3>
549
+ <p className="text-blue-700 dark:text-blue-300 text-sm">
550
+ Under the Nigeria Data Protection Regulation (NDPR), data breaches that pose a risk to the rights and freedoms of data subjects must be reported to NITDA within 72 hours of discovery.
551
+ This assessment will determine if notification is required for this breach.
552
+ </p>
553
+ </div>
554
+
555
+ {/* Submit Button */}
556
+ <div className="mt-6">
557
+ <button
558
+ type="submit"
559
+ 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}`}
560
+ >
561
+ {submitButtonText}
562
+ </button>
563
+ </div>
564
+ </div>
565
+ </form>
566
+ )}
567
+ </div>
568
+ );
569
+ };