@tantainnovative/ndpr-toolkit 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/next-env.d.ts +5 -0
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -20
- package/.eslintrc.json +0 -10
- package/.github/workflows/ci.yml +0 -36
- package/.github/workflows/nextjs.yml +0 -104
- package/.husky/commit-msg +0 -4
- package/.husky/pre-commit +0 -4
- package/.lintstagedrc.js +0 -4
- package/.nvmrc +0 -1
- package/.versionrc +0 -17
- package/CLAUDE.md +0 -90
- package/commitlint.config.js +0 -36
- package/eslint.config.mjs +0 -16
- package/jest.config.js +0 -31
- package/jest.setup.js +0 -15
- package/next.config.js +0 -15
- package/next.config.ts +0 -62
- package/packages/ndpr-toolkit/README.md +0 -467
- package/packages/ndpr-toolkit/jest.config.js +0 -23
- package/packages/ndpr-toolkit/package-lock.json +0 -8197
- package/packages/ndpr-toolkit/package.json +0 -71
- package/packages/ndpr-toolkit/rollup.config.js +0 -34
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +0 -119
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +0 -122
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +0 -270
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +0 -199
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +0 -224
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +0 -104
- package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +0 -161
- package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +0 -330
- package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +0 -149
- package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +0 -88
- package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +0 -160
- package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +0 -110
- package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +0 -97
- package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +0 -701
- package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +0 -631
- package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +0 -569
- package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +0 -496
- package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +0 -270
- package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +0 -217
- package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +0 -206
- package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +0 -342
- package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +0 -373
- package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +0 -174
- package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +0 -717
- package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +0 -476
- package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +0 -620
- package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +0 -541
- package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +0 -454
- package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +0 -333
- package/packages/ndpr-toolkit/src/hooks/useBreach.ts +0 -409
- package/packages/ndpr-toolkit/src/hooks/useConsent.ts +0 -263
- package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +0 -457
- package/packages/ndpr-toolkit/src/hooks/useDSR.ts +0 -236
- package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +0 -428
- package/packages/ndpr-toolkit/src/index.ts +0 -44
- package/packages/ndpr-toolkit/src/setupTests.ts +0 -5
- package/packages/ndpr-toolkit/src/types/breach.ts +0 -283
- package/packages/ndpr-toolkit/src/types/consent.ts +0 -111
- package/packages/ndpr-toolkit/src/types/dpia.ts +0 -236
- package/packages/ndpr-toolkit/src/types/dsr.ts +0 -192
- package/packages/ndpr-toolkit/src/types/index.ts +0 -42
- package/packages/ndpr-toolkit/src/types/privacy.ts +0 -246
- package/packages/ndpr-toolkit/src/utils/breach.ts +0 -122
- package/packages/ndpr-toolkit/src/utils/consent.ts +0 -51
- package/packages/ndpr-toolkit/src/utils/dpia.ts +0 -104
- package/packages/ndpr-toolkit/src/utils/dsr.ts +0 -77
- package/packages/ndpr-toolkit/src/utils/privacy.ts +0 -100
- package/packages/ndpr-toolkit/tsconfig.json +0 -23
- package/postcss.config.mjs +0 -5
- package/src/__tests__/example.test.ts +0 -13
- package/src/__tests__/requestService.test.ts +0 -57
- package/src/app/accessibility.css +0 -70
- package/src/app/docs/components/DocLayout.tsx +0 -267
- package/src/app/docs/components/breach-notification/page.tsx +0 -797
- package/src/app/docs/components/consent-management/page.tsx +0 -576
- package/src/app/docs/components/data-subject-rights/page.tsx +0 -511
- package/src/app/docs/components/dpia-questionnaire/layout.tsx +0 -15
- package/src/app/docs/components/dpia-questionnaire/metadata.ts +0 -31
- package/src/app/docs/components/dpia-questionnaire/page.tsx +0 -666
- package/src/app/docs/components/hooks/page.tsx +0 -305
- package/src/app/docs/components/page.tsx +0 -84
- package/src/app/docs/components/privacy-policy-generator/page.tsx +0 -634
- package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +0 -123
- package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +0 -328
- package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +0 -28
- package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +0 -91
- package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +0 -118
- package/src/app/docs/guides/breach-notification-process/page.tsx +0 -39
- package/src/app/docs/guides/conducting-dpia/page.tsx +0 -593
- package/src/app/docs/guides/data-subject-requests/page.tsx +0 -666
- package/src/app/docs/guides/managing-consent/page.tsx +0 -738
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +0 -296
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +0 -145
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +0 -33
- package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +0 -99
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +0 -159
- package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +0 -38
- package/src/app/docs/guides/page.tsx +0 -67
- package/src/app/docs/layout.tsx +0 -15
- package/src/app/docs/metadata.ts +0 -31
- package/src/app/docs/page.tsx +0 -572
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +0 -123
- package/src/app/layout.tsx +0 -37
- package/src/app/ndpr-demos/breach/page.tsx +0 -354
- package/src/app/ndpr-demos/consent/page.tsx +0 -366
- package/src/app/ndpr-demos/dpia/page.tsx +0 -495
- package/src/app/ndpr-demos/dsr/page.tsx +0 -280
- package/src/app/ndpr-demos/page.tsx +0 -73
- package/src/app/ndpr-demos/policy/page.tsx +0 -771
- package/src/app/page.tsx +0 -452
- package/src/components/ErrorBoundary.tsx +0 -90
- package/src/components/breach-notification/BreachNotificationForm.tsx +0 -479
- package/src/components/consent/ConsentBanner.tsx +0 -159
- package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +0 -419
- package/src/components/docs/DocLayout.tsx +0 -289
- package/src/components/docs/index.ts +0 -2
- package/src/components/dpia/DPIAQuestionnaire.tsx +0 -483
- package/src/components/privacy-policy/PolicyGenerator.tsx +0 -1062
- package/src/components/privacy-policy/data.ts +0 -98
- package/src/components/privacy-policy/shared/CheckboxField.tsx +0 -38
- package/src/components/privacy-policy/shared/CheckboxGroup.tsx +0 -85
- package/src/components/privacy-policy/shared/FormField.tsx +0 -79
- package/src/components/privacy-policy/shared/StepIndicator.tsx +0 -86
- package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +0 -335
- package/src/components/privacy-policy/steps/DataCollectionStep.tsx +0 -231
- package/src/components/privacy-policy/steps/DataSharingStep.tsx +0 -418
- package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +0 -202
- package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +0 -172
- package/src/components/ui/Badge.tsx +0 -46
- package/src/components/ui/Button.tsx +0 -59
- package/src/components/ui/Card.tsx +0 -92
- package/src/components/ui/Checkbox.tsx +0 -57
- package/src/components/ui/FormField.tsx +0 -50
- package/src/components/ui/Input.tsx +0 -38
- package/src/components/ui/Loading.tsx +0 -201
- package/src/components/ui/Select.tsx +0 -42
- package/src/components/ui/TextArea.tsx +0 -38
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/switch.tsx +0 -31
- package/src/components/ui/tabs.tsx +0 -66
- package/src/hooks/useConsent.ts +0 -64
- package/src/hooks/useLoadingState.ts +0 -85
- package/src/lib/consentService.ts +0 -137
- package/src/lib/dpiaQuestions.ts +0 -148
- package/src/lib/requestService.ts +0 -75
- package/src/lib/sanitize.ts +0 -108
- package/src/lib/storage.ts +0 -222
- package/src/lib/utils.ts +0 -6
- package/src/types/html-to-docx.d.ts +0 -30
- package/src/types/index.ts +0 -72
- package/tailwind.config.ts +0 -65
- package/tsconfig.json +0 -41
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { BreachCategory } from '../../types/breach';
|
|
3
|
-
|
|
4
|
-
export interface BreachReportFormProps {
|
|
5
|
-
/**
|
|
6
|
-
* Available breach categories
|
|
7
|
-
*/
|
|
8
|
-
categories: BreachCategory[];
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Callback function called when form is submitted
|
|
12
|
-
*/
|
|
13
|
-
onSubmit: (formData: any) => void;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Title displayed on the form
|
|
17
|
-
* @default "Report a Data Breach"
|
|
18
|
-
*/
|
|
19
|
-
title?: string;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Description text displayed on the form
|
|
23
|
-
* @default "Use this form to report a suspected or confirmed data breach. All fields marked with * are required."
|
|
24
|
-
*/
|
|
25
|
-
formDescription?: string;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Text for the submit button
|
|
29
|
-
* @default "Submit Report"
|
|
30
|
-
*/
|
|
31
|
-
submitButtonText?: string;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Custom CSS class for the form
|
|
35
|
-
*/
|
|
36
|
-
className?: string;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Custom CSS class for the submit button
|
|
40
|
-
*/
|
|
41
|
-
buttonClassName?: string;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Whether to show a confirmation message after submission
|
|
45
|
-
* @default true
|
|
46
|
-
*/
|
|
47
|
-
showConfirmation?: boolean;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Confirmation message to display after submission
|
|
51
|
-
* @default "Your breach report has been submitted successfully. The data protection team has been notified."
|
|
52
|
-
*/
|
|
53
|
-
confirmationMessage?: string;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Whether to allow file attachments
|
|
57
|
-
* @default true
|
|
58
|
-
*/
|
|
59
|
-
allowAttachments?: boolean;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Maximum number of attachments allowed
|
|
63
|
-
* @default 5
|
|
64
|
-
*/
|
|
65
|
-
maxAttachments?: number;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Maximum file size for attachments (in bytes)
|
|
69
|
-
* @default 5242880 (5MB)
|
|
70
|
-
*/
|
|
71
|
-
maxFileSize?: number;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Allowed file types for attachments
|
|
75
|
-
* @default ['.pdf', '.jpg', '.jpeg', '.png', '.doc', '.docx', '.xls', '.xlsx', '.txt']
|
|
76
|
-
*/
|
|
77
|
-
allowedFileTypes?: string[];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const BreachReportForm: React.FC<BreachReportFormProps> = ({
|
|
81
|
-
categories,
|
|
82
|
-
onSubmit,
|
|
83
|
-
title = "Report a Data Breach",
|
|
84
|
-
formDescription = "Use this form to report a suspected or confirmed data breach. All fields marked with * are required.",
|
|
85
|
-
submitButtonText = "Submit Report",
|
|
86
|
-
className = "",
|
|
87
|
-
buttonClassName = "",
|
|
88
|
-
showConfirmation = true,
|
|
89
|
-
confirmationMessage = "Your breach report has been submitted successfully. The data protection team has been notified.",
|
|
90
|
-
allowAttachments = true,
|
|
91
|
-
maxAttachments = 5,
|
|
92
|
-
maxFileSize = 5 * 1024 * 1024, // 5MB
|
|
93
|
-
allowedFileTypes = ['.pdf', '.jpg', '.jpeg', '.png', '.doc', '.docx', '.xls', '.xlsx', '.txt']
|
|
94
|
-
}) => {
|
|
95
|
-
const [breachTitle, setBreachTitle] = useState<string>("");
|
|
96
|
-
const [description, setDescription] = useState<string>("");
|
|
97
|
-
const [category, setCategory] = useState<string>("");
|
|
98
|
-
const [discoveredAt, setDiscoveredAt] = useState<string>("");
|
|
99
|
-
const [occurredAt, setOccurredAt] = useState<string>("");
|
|
100
|
-
const [reporterName, setReporterName] = useState<string>("");
|
|
101
|
-
const [reporterEmail, setReporterEmail] = useState<string>("");
|
|
102
|
-
const [reporterDepartment, setReporterDepartment] = useState<string>("");
|
|
103
|
-
const [reporterPhone, setReporterPhone] = useState<string>("");
|
|
104
|
-
const [affectedSystems, setAffectedSystems] = useState<string[]>([]);
|
|
105
|
-
const [affectedSystemsInput, setAffectedSystemsInput] = useState<string>("");
|
|
106
|
-
const [dataTypes, setDataTypes] = useState<string[]>([]);
|
|
107
|
-
const [estimatedAffectedSubjects, setEstimatedAffectedSubjects] = useState<string>("");
|
|
108
|
-
const [status, setStatus] = useState<'ongoing' | 'contained' | 'resolved'>('ongoing');
|
|
109
|
-
const [initialActions, setInitialActions] = useState<string>("");
|
|
110
|
-
const [attachments, setAttachments] = useState<File[]>([]);
|
|
111
|
-
const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
|
|
112
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
113
|
-
|
|
114
|
-
// Handle affected systems input
|
|
115
|
-
const handleAffectedSystemsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
116
|
-
// Store the raw input value so the input field shows exactly what the user typed
|
|
117
|
-
const inputValue = e.target.value;
|
|
118
|
-
setAffectedSystemsInput(inputValue);
|
|
119
|
-
|
|
120
|
-
// Also update the parsed array for validation and submission
|
|
121
|
-
const systems = inputValue.split(',').map(system => system.trim()).filter(Boolean);
|
|
122
|
-
setAffectedSystems(systems);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// Handle data types change
|
|
126
|
-
const handleDataTypesChange = (type: string) => {
|
|
127
|
-
setDataTypes(prev => {
|
|
128
|
-
if (prev.includes(type)) {
|
|
129
|
-
return prev.filter(t => t !== type);
|
|
130
|
-
} else {
|
|
131
|
-
return [...prev, type];
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Handle file upload
|
|
137
|
-
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
138
|
-
const files = e.target.files;
|
|
139
|
-
if (!files) return;
|
|
140
|
-
|
|
141
|
-
const newFiles: File[] = [];
|
|
142
|
-
const fileErrors: Record<string, string> = {};
|
|
143
|
-
|
|
144
|
-
// Check if adding these files would exceed the maximum
|
|
145
|
-
if (attachments.length + files.length > maxAttachments) {
|
|
146
|
-
fileErrors.attachments = `Maximum of ${maxAttachments} files allowed`;
|
|
147
|
-
setErrors(prev => ({ ...prev, ...fileErrors }));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Validate each file
|
|
152
|
-
for (let i = 0; i < files.length; i++) {
|
|
153
|
-
const file = files[i];
|
|
154
|
-
|
|
155
|
-
// Check file size
|
|
156
|
-
if (file.size > maxFileSize) {
|
|
157
|
-
fileErrors.attachments = `File ${file.name} exceeds the maximum size of ${maxFileSize / (1024 * 1024)}MB`;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Check file type
|
|
162
|
-
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
|
163
|
-
if (!allowedFileTypes.includes(fileExtension)) {
|
|
164
|
-
fileErrors.attachments = `File type ${fileExtension} is not allowed`;
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
newFiles.push(file);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (Object.keys(fileErrors).length > 0) {
|
|
172
|
-
setErrors(prev => ({ ...prev, ...fileErrors }));
|
|
173
|
-
} else {
|
|
174
|
-
setAttachments(prev => [...prev, ...newFiles]);
|
|
175
|
-
setErrors(prev => ({ ...prev, attachments: '' }));
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Remove an attachment
|
|
180
|
-
const removeAttachment = (index: number) => {
|
|
181
|
-
setAttachments(prev => prev.filter((_, i) => i !== index));
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// Validate the form
|
|
185
|
-
const validateForm = () => {
|
|
186
|
-
const newErrors: Record<string, string> = {};
|
|
187
|
-
|
|
188
|
-
if (!breachTitle.trim()) {
|
|
189
|
-
newErrors.breachTitle = "Breach title is required";
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (!description.trim()) {
|
|
193
|
-
newErrors.description = "Description is required";
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (!category) {
|
|
197
|
-
newErrors.category = "Category is required";
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (!discoveredAt) {
|
|
201
|
-
newErrors.discoveredAt = "Discovery date is required";
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (!reporterName.trim()) {
|
|
205
|
-
newErrors.reporterName = "Reporter name is required";
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!reporterEmail.trim()) {
|
|
209
|
-
newErrors.reporterEmail = "Reporter email is required";
|
|
210
|
-
} else if (!/\S+@\S+\.\S+/.test(reporterEmail)) {
|
|
211
|
-
newErrors.reporterEmail = "Reporter email is invalid";
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (!reporterDepartment.trim()) {
|
|
215
|
-
newErrors.reporterDepartment = "Reporter department is required";
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (affectedSystems.length === 0) {
|
|
219
|
-
newErrors.affectedSystems = "At least one affected system is required";
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (dataTypes.length === 0) {
|
|
223
|
-
newErrors.dataTypes = "At least one data type is required";
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (estimatedAffectedSubjects && isNaN(Number(estimatedAffectedSubjects))) {
|
|
227
|
-
newErrors.estimatedAffectedSubjects = "Estimated affected subjects must be a number";
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
setErrors(newErrors);
|
|
231
|
-
return Object.keys(newErrors).length === 0;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
// Handle form submission
|
|
235
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
236
|
-
e.preventDefault();
|
|
237
|
-
|
|
238
|
-
if (!validateForm()) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const formData = {
|
|
243
|
-
title: breachTitle,
|
|
244
|
-
description,
|
|
245
|
-
category,
|
|
246
|
-
discoveredAt: new Date(discoveredAt).getTime(),
|
|
247
|
-
occurredAt: occurredAt ? new Date(occurredAt).getTime() : undefined,
|
|
248
|
-
reportedAt: Date.now(),
|
|
249
|
-
reporter: {
|
|
250
|
-
name: reporterName,
|
|
251
|
-
email: reporterEmail,
|
|
252
|
-
department: reporterDepartment,
|
|
253
|
-
phone: reporterPhone || undefined
|
|
254
|
-
},
|
|
255
|
-
affectedSystems,
|
|
256
|
-
dataTypes,
|
|
257
|
-
estimatedAffectedSubjects: estimatedAffectedSubjects ? Number(estimatedAffectedSubjects) : undefined,
|
|
258
|
-
status,
|
|
259
|
-
initialActions: initialActions || undefined,
|
|
260
|
-
attachments: attachments.map(file => ({
|
|
261
|
-
name: file.name,
|
|
262
|
-
type: file.type,
|
|
263
|
-
size: file.size,
|
|
264
|
-
file
|
|
265
|
-
}))
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
onSubmit(formData);
|
|
269
|
-
|
|
270
|
-
if (showConfirmation) {
|
|
271
|
-
setIsSubmitted(true);
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
if (isSubmitted) {
|
|
276
|
-
return (
|
|
277
|
-
<div className={`p-4 bg-green-50 dark:bg-green-900/20 rounded-md ${className}`}>
|
|
278
|
-
<h2 className="text-lg font-bold text-green-800 dark:text-green-200 mb-2">Report Submitted</h2>
|
|
279
|
-
<p className="text-green-700 dark:text-green-300">{confirmationMessage}</p>
|
|
280
|
-
<div className="mt-4 p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-md">
|
|
281
|
-
<h3 className="text-sm font-bold text-yellow-800 dark:text-yellow-200 mb-2">Important: Next Steps</h3>
|
|
282
|
-
<p className="text-yellow-700 dark:text-yellow-300 text-sm">
|
|
283
|
-
Under the 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.
|
|
284
|
-
The data protection team will assess this breach and determine if notification is required.
|
|
285
|
-
</p>
|
|
286
|
-
</div>
|
|
287
|
-
<button
|
|
288
|
-
onClick={() => setIsSubmitted(false)}
|
|
289
|
-
className={`mt-4 px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 ${buttonClassName}`}
|
|
290
|
-
>
|
|
291
|
-
Report Another Breach
|
|
292
|
-
</button>
|
|
293
|
-
</div>
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Common data types for breaches
|
|
298
|
-
const commonDataTypes = [
|
|
299
|
-
{ id: 'personal', label: 'Personal Information (names, addresses)' },
|
|
300
|
-
{ id: 'contact', label: 'Contact Information (email, phone)' },
|
|
301
|
-
{ id: 'financial', label: 'Financial Information (bank details, payment info)' },
|
|
302
|
-
{ id: 'health', label: 'Health Information' },
|
|
303
|
-
{ id: 'identification', label: 'Identification Documents (ID cards, passports)' },
|
|
304
|
-
{ id: 'login', label: 'Login Credentials' },
|
|
305
|
-
{ id: 'biometric', label: 'Biometric Data' },
|
|
306
|
-
{ id: 'children', label: 'Children\'s Data' },
|
|
307
|
-
{ id: 'location', label: 'Location Data' },
|
|
308
|
-
{ id: 'communications', label: 'Communications Content' }
|
|
309
|
-
];
|
|
310
|
-
|
|
311
|
-
return (
|
|
312
|
-
<div className={`bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md ${className}`}>
|
|
313
|
-
<h2 className="text-xl font-bold mb-2">{title}</h2>
|
|
314
|
-
<p className="mb-6 text-gray-600 dark:text-gray-300">{formDescription}</p>
|
|
315
|
-
|
|
316
|
-
<form onSubmit={handleSubmit}>
|
|
317
|
-
<div className="space-y-6">
|
|
318
|
-
{/* Basic Breach Information */}
|
|
319
|
-
<div>
|
|
320
|
-
<h3 className="text-lg font-semibold mb-3">Breach Information</h3>
|
|
321
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
322
|
-
<div>
|
|
323
|
-
<label htmlFor="breachTitle" className="block text-sm font-medium mb-1">
|
|
324
|
-
Breach Title/Summary <span className="text-red-500">*</span>
|
|
325
|
-
</label>
|
|
326
|
-
<input
|
|
327
|
-
type="text"
|
|
328
|
-
id="breachTitle"
|
|
329
|
-
value={breachTitle}
|
|
330
|
-
onChange={e => setBreachTitle(e.target.value)}
|
|
331
|
-
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"
|
|
332
|
-
required
|
|
333
|
-
/>
|
|
334
|
-
{errors.breachTitle && <p className="mt-1 text-sm text-red-500">{errors.breachTitle}</p>}
|
|
335
|
-
</div>
|
|
336
|
-
|
|
337
|
-
<div>
|
|
338
|
-
<label htmlFor="category" className="block text-sm font-medium mb-1">
|
|
339
|
-
Breach Category <span className="text-red-500">*</span>
|
|
340
|
-
</label>
|
|
341
|
-
<select
|
|
342
|
-
id="category"
|
|
343
|
-
value={category}
|
|
344
|
-
onChange={e => setCategory(e.target.value)}
|
|
345
|
-
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"
|
|
346
|
-
required
|
|
347
|
-
>
|
|
348
|
-
<option value="">Select a category</option>
|
|
349
|
-
{categories.map(cat => (
|
|
350
|
-
<option key={cat.id} value={cat.id}>
|
|
351
|
-
{cat.name}
|
|
352
|
-
</option>
|
|
353
|
-
))}
|
|
354
|
-
</select>
|
|
355
|
-
{errors.category && <p className="mt-1 text-sm text-red-500">{errors.category}</p>}
|
|
356
|
-
</div>
|
|
357
|
-
|
|
358
|
-
<div className="md:col-span-2">
|
|
359
|
-
<label htmlFor="description" className="block text-sm font-medium mb-1">
|
|
360
|
-
Detailed Description <span className="text-red-500">*</span>
|
|
361
|
-
</label>
|
|
362
|
-
<textarea
|
|
363
|
-
id="description"
|
|
364
|
-
value={description}
|
|
365
|
-
onChange={e => setDescription(e.target.value)}
|
|
366
|
-
rows={4}
|
|
367
|
-
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"
|
|
368
|
-
required
|
|
369
|
-
/>
|
|
370
|
-
{errors.description && <p className="mt-1 text-sm text-red-500">{errors.description}</p>}
|
|
371
|
-
</div>
|
|
372
|
-
|
|
373
|
-
<div>
|
|
374
|
-
<label htmlFor="discoveredAt" className="block text-sm font-medium mb-1">
|
|
375
|
-
Date Discovered <span className="text-red-500">*</span>
|
|
376
|
-
</label>
|
|
377
|
-
<input
|
|
378
|
-
type="datetime-local"
|
|
379
|
-
id="discoveredAt"
|
|
380
|
-
value={discoveredAt}
|
|
381
|
-
onChange={e => setDiscoveredAt(e.target.value)}
|
|
382
|
-
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"
|
|
383
|
-
required
|
|
384
|
-
/>
|
|
385
|
-
{errors.discoveredAt && <p className="mt-1 text-sm text-red-500">{errors.discoveredAt}</p>}
|
|
386
|
-
</div>
|
|
387
|
-
|
|
388
|
-
<div>
|
|
389
|
-
<label htmlFor="occurredAt" className="block text-sm font-medium mb-1">
|
|
390
|
-
Date Occurred (if known)
|
|
391
|
-
</label>
|
|
392
|
-
<input
|
|
393
|
-
type="datetime-local"
|
|
394
|
-
id="occurredAt"
|
|
395
|
-
value={occurredAt}
|
|
396
|
-
onChange={e => setOccurredAt(e.target.value)}
|
|
397
|
-
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"
|
|
398
|
-
/>
|
|
399
|
-
</div>
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
|
|
403
|
-
{/* Reporter Information */}
|
|
404
|
-
<div>
|
|
405
|
-
<h3 className="text-lg font-semibold mb-3">Reporter Information</h3>
|
|
406
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
407
|
-
<div>
|
|
408
|
-
<label htmlFor="reporterName" className="block text-sm font-medium mb-1">
|
|
409
|
-
Your Name <span className="text-red-500">*</span>
|
|
410
|
-
</label>
|
|
411
|
-
<input
|
|
412
|
-
type="text"
|
|
413
|
-
id="reporterName"
|
|
414
|
-
value={reporterName}
|
|
415
|
-
onChange={e => setReporterName(e.target.value)}
|
|
416
|
-
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"
|
|
417
|
-
required
|
|
418
|
-
/>
|
|
419
|
-
{errors.reporterName && <p className="mt-1 text-sm text-red-500">{errors.reporterName}</p>}
|
|
420
|
-
</div>
|
|
421
|
-
|
|
422
|
-
<div>
|
|
423
|
-
<label htmlFor="reporterEmail" className="block text-sm font-medium mb-1">
|
|
424
|
-
Your Email <span className="text-red-500">*</span>
|
|
425
|
-
</label>
|
|
426
|
-
<input
|
|
427
|
-
type="email"
|
|
428
|
-
id="reporterEmail"
|
|
429
|
-
value={reporterEmail}
|
|
430
|
-
onChange={e => setReporterEmail(e.target.value)}
|
|
431
|
-
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"
|
|
432
|
-
required
|
|
433
|
-
/>
|
|
434
|
-
{errors.reporterEmail && <p className="mt-1 text-sm text-red-500">{errors.reporterEmail}</p>}
|
|
435
|
-
</div>
|
|
436
|
-
|
|
437
|
-
<div>
|
|
438
|
-
<label htmlFor="reporterDepartment" className="block text-sm font-medium mb-1">
|
|
439
|
-
Your Department <span className="text-red-500">*</span>
|
|
440
|
-
</label>
|
|
441
|
-
<input
|
|
442
|
-
type="text"
|
|
443
|
-
id="reporterDepartment"
|
|
444
|
-
value={reporterDepartment}
|
|
445
|
-
onChange={e => setReporterDepartment(e.target.value)}
|
|
446
|
-
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"
|
|
447
|
-
required
|
|
448
|
-
/>
|
|
449
|
-
{errors.reporterDepartment && <p className="mt-1 text-sm text-red-500">{errors.reporterDepartment}</p>}
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
<div>
|
|
453
|
-
<label htmlFor="reporterPhone" className="block text-sm font-medium mb-1">
|
|
454
|
-
Your Phone Number (Optional)
|
|
455
|
-
</label>
|
|
456
|
-
<input
|
|
457
|
-
type="tel"
|
|
458
|
-
id="reporterPhone"
|
|
459
|
-
value={reporterPhone}
|
|
460
|
-
onChange={e => setReporterPhone(e.target.value)}
|
|
461
|
-
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"
|
|
462
|
-
/>
|
|
463
|
-
</div>
|
|
464
|
-
</div>
|
|
465
|
-
</div>
|
|
466
|
-
|
|
467
|
-
{/* Impact Information */}
|
|
468
|
-
<div>
|
|
469
|
-
<h3 className="text-lg font-semibold mb-3">Impact Information</h3>
|
|
470
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
471
|
-
<div>
|
|
472
|
-
<label htmlFor="affectedSystems" className="block text-sm font-medium mb-1">
|
|
473
|
-
Affected Systems/Applications <span className="text-red-500">*</span>
|
|
474
|
-
</label>
|
|
475
|
-
<input
|
|
476
|
-
type="text"
|
|
477
|
-
id="affectedSystems"
|
|
478
|
-
value={affectedSystemsInput}
|
|
479
|
-
onChange={handleAffectedSystemsChange}
|
|
480
|
-
placeholder="e.g. CRM, Email Server, Website (comma separated)"
|
|
481
|
-
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"
|
|
482
|
-
required
|
|
483
|
-
/>
|
|
484
|
-
{errors.affectedSystems && <p className="mt-1 text-sm text-red-500">{errors.affectedSystems}</p>}
|
|
485
|
-
</div>
|
|
486
|
-
|
|
487
|
-
<div>
|
|
488
|
-
<label htmlFor="estimatedAffectedSubjects" className="block text-sm font-medium mb-1">
|
|
489
|
-
Estimated Number of Affected Data Subjects
|
|
490
|
-
</label>
|
|
491
|
-
<input
|
|
492
|
-
type="text"
|
|
493
|
-
id="estimatedAffectedSubjects"
|
|
494
|
-
value={estimatedAffectedSubjects}
|
|
495
|
-
onChange={e => setEstimatedAffectedSubjects(e.target.value)}
|
|
496
|
-
placeholder="e.g. 100"
|
|
497
|
-
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"
|
|
498
|
-
/>
|
|
499
|
-
{errors.estimatedAffectedSubjects && <p className="mt-1 text-sm text-red-500">{errors.estimatedAffectedSubjects}</p>}
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
<div className="md:col-span-2">
|
|
503
|
-
<label className="block text-sm font-medium mb-1">
|
|
504
|
-
Types of Data Involved <span className="text-red-500">*</span>
|
|
505
|
-
</label>
|
|
506
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
507
|
-
{commonDataTypes.map(type => (
|
|
508
|
-
<div key={type.id} className="flex items-center">
|
|
509
|
-
<input
|
|
510
|
-
type="checkbox"
|
|
511
|
-
id={`dataType_${type.id}`}
|
|
512
|
-
checked={dataTypes.includes(type.id)}
|
|
513
|
-
onChange={() => handleDataTypesChange(type.id)}
|
|
514
|
-
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
515
|
-
/>
|
|
516
|
-
<label htmlFor={`dataType_${type.id}`} className="ml-2 text-sm text-gray-700 dark:text-gray-300">
|
|
517
|
-
{type.label}
|
|
518
|
-
</label>
|
|
519
|
-
</div>
|
|
520
|
-
))}
|
|
521
|
-
</div>
|
|
522
|
-
{errors.dataTypes && <p className="mt-1 text-sm text-red-500">{errors.dataTypes}</p>}
|
|
523
|
-
</div>
|
|
524
|
-
|
|
525
|
-
<div>
|
|
526
|
-
<label htmlFor="status" className="block text-sm font-medium mb-1">
|
|
527
|
-
Current Status <span className="text-red-500">*</span>
|
|
528
|
-
</label>
|
|
529
|
-
<select
|
|
530
|
-
id="status"
|
|
531
|
-
value={status}
|
|
532
|
-
onChange={e => setStatus(e.target.value as 'ongoing' | 'contained' | 'resolved')}
|
|
533
|
-
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"
|
|
534
|
-
required
|
|
535
|
-
>
|
|
536
|
-
<option value="ongoing">Ongoing (breach is still active)</option>
|
|
537
|
-
<option value="contained">Contained (breach is stopped but not resolved)</option>
|
|
538
|
-
<option value="resolved">Resolved (breach is fully addressed)</option>
|
|
539
|
-
</select>
|
|
540
|
-
</div>
|
|
541
|
-
|
|
542
|
-
<div className="md:col-span-2">
|
|
543
|
-
<label htmlFor="initialActions" className="block text-sm font-medium mb-1">
|
|
544
|
-
Initial Actions Taken
|
|
545
|
-
</label>
|
|
546
|
-
<textarea
|
|
547
|
-
id="initialActions"
|
|
548
|
-
value={initialActions}
|
|
549
|
-
onChange={e => setInitialActions(e.target.value)}
|
|
550
|
-
placeholder="Describe any immediate actions that have been taken to address the breach"
|
|
551
|
-
rows={3}
|
|
552
|
-
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"
|
|
553
|
-
/>
|
|
554
|
-
</div>
|
|
555
|
-
</div>
|
|
556
|
-
</div>
|
|
557
|
-
|
|
558
|
-
{/* Attachments */}
|
|
559
|
-
{allowAttachments && (
|
|
560
|
-
<div>
|
|
561
|
-
<h3 className="text-lg font-semibold mb-3">Attachments</h3>
|
|
562
|
-
<div className="mb-4">
|
|
563
|
-
<label className="block text-sm font-medium mb-1">
|
|
564
|
-
Upload Supporting Files (Optional)
|
|
565
|
-
</label>
|
|
566
|
-
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
567
|
-
Max {maxAttachments} files, {maxFileSize / (1024 * 1024)}MB each. Allowed types: {allowedFileTypes.join(', ')}
|
|
568
|
-
</p>
|
|
569
|
-
<input
|
|
570
|
-
type="file"
|
|
571
|
-
onChange={handleFileUpload}
|
|
572
|
-
multiple
|
|
573
|
-
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"
|
|
574
|
-
accept={allowedFileTypes.join(',')}
|
|
575
|
-
/>
|
|
576
|
-
{errors.attachments && <p className="mt-1 text-sm text-red-500">{errors.attachments}</p>}
|
|
577
|
-
</div>
|
|
578
|
-
|
|
579
|
-
{attachments.length > 0 && (
|
|
580
|
-
<div className="mb-4">
|
|
581
|
-
<h4 className="text-sm font-medium mb-2">Attached Files:</h4>
|
|
582
|
-
<ul className="space-y-2">
|
|
583
|
-
{attachments.map((file, index) => (
|
|
584
|
-
<li key={index} className="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-700 rounded">
|
|
585
|
-
<div className="flex items-center">
|
|
586
|
-
<svg className="w-4 h-4 text-gray-500 dark:text-gray-400 mr-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
587
|
-
<path fillRule="evenodd" d="M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z" clipRule="evenodd" />
|
|
588
|
-
</svg>
|
|
589
|
-
<span className="text-sm text-gray-700 dark:text-gray-300">{file.name}</span>
|
|
590
|
-
<span className="ml-2 text-xs text-gray-500 dark:text-gray-400">({(file.size / 1024).toFixed(1)} KB)</span>
|
|
591
|
-
</div>
|
|
592
|
-
<button
|
|
593
|
-
type="button"
|
|
594
|
-
onClick={() => removeAttachment(index)}
|
|
595
|
-
className="text-red-500 hover:text-red-700"
|
|
596
|
-
>
|
|
597
|
-
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
598
|
-
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
|
|
599
|
-
</svg>
|
|
600
|
-
</button>
|
|
601
|
-
</li>
|
|
602
|
-
))}
|
|
603
|
-
</ul>
|
|
604
|
-
</div>
|
|
605
|
-
)}
|
|
606
|
-
</div>
|
|
607
|
-
)}
|
|
608
|
-
|
|
609
|
-
{/* NDPR Notice */}
|
|
610
|
-
<div className="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-md">
|
|
611
|
-
<h3 className="text-sm font-bold text-blue-800 dark:text-blue-200 mb-2">NDPR Breach Notification Requirements</h3>
|
|
612
|
-
<p className="text-blue-700 dark:text-blue-300 text-sm">
|
|
613
|
-
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.
|
|
614
|
-
The data protection team will assess this breach and determine if notification is required.
|
|
615
|
-
</p>
|
|
616
|
-
</div>
|
|
617
|
-
|
|
618
|
-
{/* Submit Button */}
|
|
619
|
-
<div className="mt-6">
|
|
620
|
-
<button
|
|
621
|
-
type="submit"
|
|
622
|
-
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}`}
|
|
623
|
-
>
|
|
624
|
-
{submitButtonText}
|
|
625
|
-
</button>
|
|
626
|
-
</div>
|
|
627
|
-
</div>
|
|
628
|
-
</form>
|
|
629
|
-
</div>
|
|
630
|
-
);
|
|
631
|
-
};
|