@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.
Files changed (156) hide show
  1. package/next-env.d.ts +5 -0
  2. package/package.json +1 -1
  3. package/.claude/settings.local.json +0 -20
  4. package/.eslintrc.json +0 -10
  5. package/.github/workflows/ci.yml +0 -36
  6. package/.github/workflows/nextjs.yml +0 -104
  7. package/.husky/commit-msg +0 -4
  8. package/.husky/pre-commit +0 -4
  9. package/.lintstagedrc.js +0 -4
  10. package/.nvmrc +0 -1
  11. package/.versionrc +0 -17
  12. package/CLAUDE.md +0 -90
  13. package/commitlint.config.js +0 -36
  14. package/eslint.config.mjs +0 -16
  15. package/jest.config.js +0 -31
  16. package/jest.setup.js +0 -15
  17. package/next.config.js +0 -15
  18. package/next.config.ts +0 -62
  19. package/packages/ndpr-toolkit/README.md +0 -467
  20. package/packages/ndpr-toolkit/jest.config.js +0 -23
  21. package/packages/ndpr-toolkit/package-lock.json +0 -8197
  22. package/packages/ndpr-toolkit/package.json +0 -71
  23. package/packages/ndpr-toolkit/rollup.config.js +0 -34
  24. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +0 -119
  25. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +0 -122
  26. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +0 -270
  27. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +0 -199
  28. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +0 -224
  29. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +0 -104
  30. package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +0 -161
  31. package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +0 -330
  32. package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +0 -149
  33. package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +0 -88
  34. package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +0 -160
  35. package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +0 -110
  36. package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +0 -97
  37. package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +0 -701
  38. package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +0 -631
  39. package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +0 -569
  40. package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +0 -496
  41. package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +0 -270
  42. package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +0 -217
  43. package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +0 -206
  44. package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +0 -342
  45. package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +0 -373
  46. package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +0 -174
  47. package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +0 -717
  48. package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +0 -476
  49. package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +0 -620
  50. package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +0 -541
  51. package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +0 -454
  52. package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +0 -333
  53. package/packages/ndpr-toolkit/src/hooks/useBreach.ts +0 -409
  54. package/packages/ndpr-toolkit/src/hooks/useConsent.ts +0 -263
  55. package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +0 -457
  56. package/packages/ndpr-toolkit/src/hooks/useDSR.ts +0 -236
  57. package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +0 -428
  58. package/packages/ndpr-toolkit/src/index.ts +0 -44
  59. package/packages/ndpr-toolkit/src/setupTests.ts +0 -5
  60. package/packages/ndpr-toolkit/src/types/breach.ts +0 -283
  61. package/packages/ndpr-toolkit/src/types/consent.ts +0 -111
  62. package/packages/ndpr-toolkit/src/types/dpia.ts +0 -236
  63. package/packages/ndpr-toolkit/src/types/dsr.ts +0 -192
  64. package/packages/ndpr-toolkit/src/types/index.ts +0 -42
  65. package/packages/ndpr-toolkit/src/types/privacy.ts +0 -246
  66. package/packages/ndpr-toolkit/src/utils/breach.ts +0 -122
  67. package/packages/ndpr-toolkit/src/utils/consent.ts +0 -51
  68. package/packages/ndpr-toolkit/src/utils/dpia.ts +0 -104
  69. package/packages/ndpr-toolkit/src/utils/dsr.ts +0 -77
  70. package/packages/ndpr-toolkit/src/utils/privacy.ts +0 -100
  71. package/packages/ndpr-toolkit/tsconfig.json +0 -23
  72. package/postcss.config.mjs +0 -5
  73. package/src/__tests__/example.test.ts +0 -13
  74. package/src/__tests__/requestService.test.ts +0 -57
  75. package/src/app/accessibility.css +0 -70
  76. package/src/app/docs/components/DocLayout.tsx +0 -267
  77. package/src/app/docs/components/breach-notification/page.tsx +0 -797
  78. package/src/app/docs/components/consent-management/page.tsx +0 -576
  79. package/src/app/docs/components/data-subject-rights/page.tsx +0 -511
  80. package/src/app/docs/components/dpia-questionnaire/layout.tsx +0 -15
  81. package/src/app/docs/components/dpia-questionnaire/metadata.ts +0 -31
  82. package/src/app/docs/components/dpia-questionnaire/page.tsx +0 -666
  83. package/src/app/docs/components/hooks/page.tsx +0 -305
  84. package/src/app/docs/components/page.tsx +0 -84
  85. package/src/app/docs/components/privacy-policy-generator/page.tsx +0 -634
  86. package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +0 -123
  87. package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +0 -328
  88. package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +0 -28
  89. package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +0 -91
  90. package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +0 -118
  91. package/src/app/docs/guides/breach-notification-process/page.tsx +0 -39
  92. package/src/app/docs/guides/conducting-dpia/page.tsx +0 -593
  93. package/src/app/docs/guides/data-subject-requests/page.tsx +0 -666
  94. package/src/app/docs/guides/managing-consent/page.tsx +0 -738
  95. package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +0 -296
  96. package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +0 -145
  97. package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +0 -33
  98. package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +0 -99
  99. package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +0 -159
  100. package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +0 -38
  101. package/src/app/docs/guides/page.tsx +0 -67
  102. package/src/app/docs/layout.tsx +0 -15
  103. package/src/app/docs/metadata.ts +0 -31
  104. package/src/app/docs/page.tsx +0 -572
  105. package/src/app/favicon.ico +0 -0
  106. package/src/app/globals.css +0 -123
  107. package/src/app/layout.tsx +0 -37
  108. package/src/app/ndpr-demos/breach/page.tsx +0 -354
  109. package/src/app/ndpr-demos/consent/page.tsx +0 -366
  110. package/src/app/ndpr-demos/dpia/page.tsx +0 -495
  111. package/src/app/ndpr-demos/dsr/page.tsx +0 -280
  112. package/src/app/ndpr-demos/page.tsx +0 -73
  113. package/src/app/ndpr-demos/policy/page.tsx +0 -771
  114. package/src/app/page.tsx +0 -452
  115. package/src/components/ErrorBoundary.tsx +0 -90
  116. package/src/components/breach-notification/BreachNotificationForm.tsx +0 -479
  117. package/src/components/consent/ConsentBanner.tsx +0 -159
  118. package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +0 -419
  119. package/src/components/docs/DocLayout.tsx +0 -289
  120. package/src/components/docs/index.ts +0 -2
  121. package/src/components/dpia/DPIAQuestionnaire.tsx +0 -483
  122. package/src/components/privacy-policy/PolicyGenerator.tsx +0 -1062
  123. package/src/components/privacy-policy/data.ts +0 -98
  124. package/src/components/privacy-policy/shared/CheckboxField.tsx +0 -38
  125. package/src/components/privacy-policy/shared/CheckboxGroup.tsx +0 -85
  126. package/src/components/privacy-policy/shared/FormField.tsx +0 -79
  127. package/src/components/privacy-policy/shared/StepIndicator.tsx +0 -86
  128. package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +0 -335
  129. package/src/components/privacy-policy/steps/DataCollectionStep.tsx +0 -231
  130. package/src/components/privacy-policy/steps/DataSharingStep.tsx +0 -418
  131. package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +0 -202
  132. package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +0 -172
  133. package/src/components/ui/Badge.tsx +0 -46
  134. package/src/components/ui/Button.tsx +0 -59
  135. package/src/components/ui/Card.tsx +0 -92
  136. package/src/components/ui/Checkbox.tsx +0 -57
  137. package/src/components/ui/FormField.tsx +0 -50
  138. package/src/components/ui/Input.tsx +0 -38
  139. package/src/components/ui/Loading.tsx +0 -201
  140. package/src/components/ui/Select.tsx +0 -42
  141. package/src/components/ui/TextArea.tsx +0 -38
  142. package/src/components/ui/label.tsx +0 -24
  143. package/src/components/ui/switch.tsx +0 -31
  144. package/src/components/ui/tabs.tsx +0 -66
  145. package/src/hooks/useConsent.ts +0 -64
  146. package/src/hooks/useLoadingState.ts +0 -85
  147. package/src/lib/consentService.ts +0 -137
  148. package/src/lib/dpiaQuestions.ts +0 -148
  149. package/src/lib/requestService.ts +0 -75
  150. package/src/lib/sanitize.ts +0 -108
  151. package/src/lib/storage.ts +0 -222
  152. package/src/lib/utils.ts +0 -6
  153. package/src/types/html-to-docx.d.ts +0 -30
  154. package/src/types/index.ts +0 -72
  155. package/tailwind.config.ts +0 -65
  156. 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
- };