@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,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
|
+
};
|