@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,476 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { RequestType } from '../../types/dsr';
|
|
3
|
+
|
|
4
|
+
export interface DSRRequestFormProps {
|
|
5
|
+
/**
|
|
6
|
+
* Array of request types that can be submitted
|
|
7
|
+
*/
|
|
8
|
+
requestTypes: RequestType[];
|
|
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 "Submit a Data Subject Request"
|
|
18
|
+
*/
|
|
19
|
+
title?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Description text displayed on the form
|
|
23
|
+
* @default "Use this form to exercise your rights under the Nigeria Data Protection Regulation (NDPR)."
|
|
24
|
+
*/
|
|
25
|
+
description?: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Text for the submit button
|
|
29
|
+
* @default "Submit Request"
|
|
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 request has been submitted successfully. You will receive a confirmation email shortly."
|
|
52
|
+
*/
|
|
53
|
+
confirmationMessage?: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether to require identity verification
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
requireIdentityVerification?: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Types of identifiers accepted for verification
|
|
63
|
+
* @default ["email", "account", "customer_id"]
|
|
64
|
+
*/
|
|
65
|
+
identifierTypes?: Array<{
|
|
66
|
+
id: string;
|
|
67
|
+
label: string;
|
|
68
|
+
}>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether to collect additional contact information
|
|
72
|
+
* @default true
|
|
73
|
+
*/
|
|
74
|
+
collectAdditionalContact?: boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Custom labels for form fields
|
|
78
|
+
*/
|
|
79
|
+
labels?: {
|
|
80
|
+
name?: string;
|
|
81
|
+
email?: string;
|
|
82
|
+
requestType?: string;
|
|
83
|
+
description?: string;
|
|
84
|
+
submit?: string;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const DSRRequestForm: React.FC<DSRRequestFormProps> = ({
|
|
89
|
+
requestTypes,
|
|
90
|
+
onSubmit,
|
|
91
|
+
title = "Submit a Data Subject Request",
|
|
92
|
+
description = "Use this form to exercise your rights under the Nigeria Data Protection Regulation (NDPR).",
|
|
93
|
+
submitButtonText = "Submit Request",
|
|
94
|
+
className = "",
|
|
95
|
+
buttonClassName = "",
|
|
96
|
+
showConfirmation = true,
|
|
97
|
+
confirmationMessage = "Your request has been submitted successfully. You will receive a confirmation email shortly.",
|
|
98
|
+
requireIdentityVerification = true,
|
|
99
|
+
identifierTypes = [
|
|
100
|
+
{ id: "email", label: "Email Address" },
|
|
101
|
+
{ id: "account", label: "Account Number" },
|
|
102
|
+
{ id: "customer_id", label: "Customer ID" }
|
|
103
|
+
],
|
|
104
|
+
collectAdditionalContact = true,
|
|
105
|
+
labels = {}
|
|
106
|
+
}) => {
|
|
107
|
+
const [selectedRequestType, setSelectedRequestType] = useState<string>("");
|
|
108
|
+
const [fullName, setFullName] = useState<string>("");
|
|
109
|
+
const [email, setEmail] = useState<string>("");
|
|
110
|
+
const [phone, setPhone] = useState<string>("");
|
|
111
|
+
const [identifierType, setIdentifierType] = useState<string>(identifierTypes[0]?.id || "");
|
|
112
|
+
const [identifierValue, setIdentifierValue] = useState<string>("");
|
|
113
|
+
const [additionalInfo, setAdditionalInfo] = useState<Record<string, any>>({});
|
|
114
|
+
const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
|
|
115
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
116
|
+
|
|
117
|
+
const selectedType = requestTypes.find(type => type.id === selectedRequestType);
|
|
118
|
+
|
|
119
|
+
const handleRequestTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
120
|
+
setSelectedRequestType(e.target.value);
|
|
121
|
+
setAdditionalInfo({});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleAdditionalInfoChange = (id: string, value: any) => {
|
|
125
|
+
setAdditionalInfo(prev => ({
|
|
126
|
+
...prev,
|
|
127
|
+
[id]: value
|
|
128
|
+
}));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const validateForm = () => {
|
|
132
|
+
const newErrors: Record<string, string> = {};
|
|
133
|
+
|
|
134
|
+
if (!fullName.trim()) {
|
|
135
|
+
newErrors.fullName = "Full name is required";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!email.trim()) {
|
|
139
|
+
newErrors.email = "Email is required";
|
|
140
|
+
} else if (!/\S+@\S+\.\S+/.test(email)) {
|
|
141
|
+
newErrors.email = "Email is invalid";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!selectedRequestType) {
|
|
145
|
+
newErrors.requestType = "Please select a request type";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (requireIdentityVerification && !identifierValue.trim()) {
|
|
149
|
+
newErrors.identifierValue = "Identifier value is required";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Validate additional fields if required
|
|
153
|
+
if (selectedType?.requiresAdditionalInfo && selectedType.additionalFields) {
|
|
154
|
+
selectedType.additionalFields.forEach(field => {
|
|
155
|
+
if (field.required && !additionalInfo[field.id]) {
|
|
156
|
+
newErrors[`additional_${field.id}`] = `${field.label} is required`;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setErrors(newErrors);
|
|
162
|
+
return Object.keys(newErrors).length === 0;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
|
|
168
|
+
if (!validateForm()) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const formData = {
|
|
173
|
+
requestType: selectedRequestType,
|
|
174
|
+
dataSubject: {
|
|
175
|
+
fullName,
|
|
176
|
+
email,
|
|
177
|
+
phone: phone || undefined,
|
|
178
|
+
identifierType,
|
|
179
|
+
identifierValue
|
|
180
|
+
},
|
|
181
|
+
additionalInfo: Object.keys(additionalInfo).length > 0 ? additionalInfo : undefined,
|
|
182
|
+
submittedAt: Date.now()
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
onSubmit(formData);
|
|
186
|
+
|
|
187
|
+
if (showConfirmation) {
|
|
188
|
+
setIsSubmitted(true);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (isSubmitted) {
|
|
193
|
+
return (
|
|
194
|
+
<div className={`p-4 bg-green-50 dark:bg-green-900/20 rounded-md ${className}`}>
|
|
195
|
+
<h2 className="text-lg font-bold text-green-800 dark:text-green-200 mb-2">Request Submitted</h2>
|
|
196
|
+
<p className="text-green-700 dark:text-green-300">{confirmationMessage}</p>
|
|
197
|
+
<button
|
|
198
|
+
onClick={() => setIsSubmitted(false)}
|
|
199
|
+
className={`mt-4 px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 ${buttonClassName}`}
|
|
200
|
+
>
|
|
201
|
+
Submit Another Request
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
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
|
+
<form onSubmit={handleSubmit}>
|
|
213
|
+
<div className="space-y-4">
|
|
214
|
+
{/* Personal Information */}
|
|
215
|
+
<div>
|
|
216
|
+
<h3 className="text-lg font-semibold mb-3">Personal Information</h3>
|
|
217
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
218
|
+
<div>
|
|
219
|
+
<label htmlFor="fullName" className="block text-sm font-medium mb-1">
|
|
220
|
+
{labels.name || "Full Name"} <span className="text-red-500">*</span>
|
|
221
|
+
</label>
|
|
222
|
+
<input
|
|
223
|
+
type="text"
|
|
224
|
+
id="fullName"
|
|
225
|
+
value={fullName}
|
|
226
|
+
onChange={e => setFullName(e.target.value)}
|
|
227
|
+
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"
|
|
228
|
+
required
|
|
229
|
+
/>
|
|
230
|
+
{errors.fullName && <p className="mt-1 text-sm text-red-500">{errors.fullName}</p>}
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div>
|
|
234
|
+
<label htmlFor="email" className="block text-sm font-medium mb-1">
|
|
235
|
+
{labels.email || "Email Address"} <span className="text-red-500">*</span>
|
|
236
|
+
</label>
|
|
237
|
+
<input
|
|
238
|
+
type="email"
|
|
239
|
+
id="email"
|
|
240
|
+
value={email}
|
|
241
|
+
onChange={e => setEmail(e.target.value)}
|
|
242
|
+
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"
|
|
243
|
+
required
|
|
244
|
+
/>
|
|
245
|
+
{errors.email && <p className="mt-1 text-sm text-red-500">{errors.email}</p>}
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
{collectAdditionalContact && (
|
|
249
|
+
<div>
|
|
250
|
+
<label htmlFor="phone" className="block text-sm font-medium mb-1">
|
|
251
|
+
Phone Number (Optional)
|
|
252
|
+
</label>
|
|
253
|
+
<input
|
|
254
|
+
type="tel"
|
|
255
|
+
id="phone"
|
|
256
|
+
value={phone}
|
|
257
|
+
onChange={e => setPhone(e.target.value)}
|
|
258
|
+
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"
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
{/* Request Type */}
|
|
266
|
+
<div>
|
|
267
|
+
<h3 className="text-lg font-semibold mb-3">Request Details</h3>
|
|
268
|
+
<div className="mb-4">
|
|
269
|
+
<label htmlFor="requestType" className="block text-sm font-medium mb-1">
|
|
270
|
+
{labels.requestType || "Request Type"} <span className="text-red-500">*</span>
|
|
271
|
+
</label>
|
|
272
|
+
<select
|
|
273
|
+
id="requestType"
|
|
274
|
+
value={selectedRequestType}
|
|
275
|
+
onChange={handleRequestTypeChange}
|
|
276
|
+
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"
|
|
277
|
+
required
|
|
278
|
+
>
|
|
279
|
+
<option value="">Select a request type</option>
|
|
280
|
+
{requestTypes.map(type => (
|
|
281
|
+
<option key={type.id} value={type.id}>
|
|
282
|
+
{type.name}
|
|
283
|
+
</option>
|
|
284
|
+
))}
|
|
285
|
+
</select>
|
|
286
|
+
{errors.requestType && <p className="mt-1 text-sm text-red-500">{errors.requestType}</p>}
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
{selectedType && (
|
|
290
|
+
<div className="mb-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-md">
|
|
291
|
+
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">{selectedType.description}</p>
|
|
292
|
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
293
|
+
Estimated completion time: {selectedType.estimatedCompletionTime} {selectedType.estimatedCompletionTime === 1 ? 'day' : 'days'}
|
|
294
|
+
</p>
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
<div className="mb-4">
|
|
299
|
+
<label htmlFor="requestDescription" className="block text-sm font-medium mb-1">
|
|
300
|
+
{labels.description || "Additional Information"}
|
|
301
|
+
</label>
|
|
302
|
+
<textarea
|
|
303
|
+
id="requestDescription"
|
|
304
|
+
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"
|
|
305
|
+
rows={4}
|
|
306
|
+
placeholder="Please provide any additional details that might help us process your request"
|
|
307
|
+
/>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{/* Identity Verification */}
|
|
312
|
+
{requireIdentityVerification && (
|
|
313
|
+
<div>
|
|
314
|
+
<h3 className="text-lg font-semibold mb-3">Identity Verification</h3>
|
|
315
|
+
<p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
|
|
316
|
+
To protect your privacy, we need to verify your identity before processing your request.
|
|
317
|
+
</p>
|
|
318
|
+
|
|
319
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
320
|
+
<div>
|
|
321
|
+
<label htmlFor="identifierType" className="block text-sm font-medium mb-1">
|
|
322
|
+
Identifier Type <span className="text-red-500">*</span>
|
|
323
|
+
</label>
|
|
324
|
+
<select
|
|
325
|
+
id="identifierType"
|
|
326
|
+
value={identifierType}
|
|
327
|
+
onChange={e => setIdentifierType(e.target.value)}
|
|
328
|
+
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"
|
|
329
|
+
required
|
|
330
|
+
>
|
|
331
|
+
{identifierTypes.map(type => (
|
|
332
|
+
<option key={type.id} value={type.id}>
|
|
333
|
+
{type.label}
|
|
334
|
+
</option>
|
|
335
|
+
))}
|
|
336
|
+
</select>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div>
|
|
340
|
+
<label htmlFor="identifierValue" className="block text-sm font-medium mb-1">
|
|
341
|
+
Identifier Value <span className="text-red-500">*</span>
|
|
342
|
+
</label>
|
|
343
|
+
<input
|
|
344
|
+
type="text"
|
|
345
|
+
id="identifierValue"
|
|
346
|
+
value={identifierValue}
|
|
347
|
+
onChange={e => setIdentifierValue(e.target.value)}
|
|
348
|
+
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"
|
|
349
|
+
required
|
|
350
|
+
/>
|
|
351
|
+
{errors.identifierValue && <p className="mt-1 text-sm text-red-500">{errors.identifierValue}</p>}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
{/* Additional Information */}
|
|
358
|
+
{selectedType?.requiresAdditionalInfo && selectedType.additionalFields && selectedType.additionalFields.length > 0 && (
|
|
359
|
+
<div>
|
|
360
|
+
<h3 className="text-lg font-semibold mb-3">Additional Information</h3>
|
|
361
|
+
<div className="space-y-4">
|
|
362
|
+
{selectedType.additionalFields.map(field => (
|
|
363
|
+
<div key={field.id}>
|
|
364
|
+
<label htmlFor={field.id} className="block text-sm font-medium mb-1">
|
|
365
|
+
{field.label} {field.required && <span className="text-red-500">*</span>}
|
|
366
|
+
</label>
|
|
367
|
+
|
|
368
|
+
{field.type === 'text' && (
|
|
369
|
+
<input
|
|
370
|
+
type="text"
|
|
371
|
+
id={field.id}
|
|
372
|
+
value={additionalInfo[field.id] || ''}
|
|
373
|
+
onChange={e => handleAdditionalInfoChange(field.id, e.target.value)}
|
|
374
|
+
placeholder={field.placeholder}
|
|
375
|
+
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"
|
|
376
|
+
required={field.required}
|
|
377
|
+
/>
|
|
378
|
+
)}
|
|
379
|
+
|
|
380
|
+
{field.type === 'textarea' && (
|
|
381
|
+
<textarea
|
|
382
|
+
id={field.id}
|
|
383
|
+
value={additionalInfo[field.id] || ''}
|
|
384
|
+
onChange={e => handleAdditionalInfoChange(field.id, e.target.value)}
|
|
385
|
+
placeholder={field.placeholder}
|
|
386
|
+
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"
|
|
387
|
+
rows={4}
|
|
388
|
+
required={field.required}
|
|
389
|
+
/>
|
|
390
|
+
)}
|
|
391
|
+
|
|
392
|
+
{field.type === 'select' && field.options && (
|
|
393
|
+
<select
|
|
394
|
+
id={field.id}
|
|
395
|
+
value={additionalInfo[field.id] || ''}
|
|
396
|
+
onChange={e => handleAdditionalInfoChange(field.id, 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
|
+
required={field.required}
|
|
399
|
+
>
|
|
400
|
+
<option value="">{field.placeholder || 'Select an option'}</option>
|
|
401
|
+
{field.options.map(option => (
|
|
402
|
+
<option key={option} value={option}>
|
|
403
|
+
{option}
|
|
404
|
+
</option>
|
|
405
|
+
))}
|
|
406
|
+
</select>
|
|
407
|
+
)}
|
|
408
|
+
|
|
409
|
+
{field.type === 'checkbox' && (
|
|
410
|
+
<div className="flex items-start">
|
|
411
|
+
<div className="flex items-center h-5">
|
|
412
|
+
<input
|
|
413
|
+
type="checkbox"
|
|
414
|
+
id={field.id}
|
|
415
|
+
checked={!!additionalInfo[field.id]}
|
|
416
|
+
onChange={e => handleAdditionalInfoChange(field.id, e.target.checked)}
|
|
417
|
+
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
418
|
+
required={field.required}
|
|
419
|
+
/>
|
|
420
|
+
</div>
|
|
421
|
+
<div className="ml-3 text-sm">
|
|
422
|
+
<label htmlFor={field.id} className="text-gray-700 dark:text-gray-300">
|
|
423
|
+
{field.placeholder || field.label}
|
|
424
|
+
</label>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
|
|
429
|
+
{field.type === 'file' && (
|
|
430
|
+
<input
|
|
431
|
+
type="file"
|
|
432
|
+
id={field.id}
|
|
433
|
+
onChange={e => {
|
|
434
|
+
const file = e.target.files?.[0];
|
|
435
|
+
if (file) {
|
|
436
|
+
handleAdditionalInfoChange(field.id, file);
|
|
437
|
+
}
|
|
438
|
+
}}
|
|
439
|
+
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"
|
|
440
|
+
required={field.required}
|
|
441
|
+
/>
|
|
442
|
+
)}
|
|
443
|
+
|
|
444
|
+
{errors[`additional_${field.id}`] && (
|
|
445
|
+
<p className="mt-1 text-sm text-red-500">{errors[`additional_${field.id}`]}</p>
|
|
446
|
+
)}
|
|
447
|
+
</div>
|
|
448
|
+
))}
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
)}
|
|
452
|
+
|
|
453
|
+
{/* Privacy Notice */}
|
|
454
|
+
<div className="mt-6 p-4 bg-gray-50 dark:bg-gray-700 rounded-md">
|
|
455
|
+
<h3 className="text-sm font-semibold mb-2">Privacy Notice</h3>
|
|
456
|
+
<p className="text-xs text-gray-600 dark:text-gray-300">
|
|
457
|
+
The information you provide in this form will be used solely for the purpose of processing your data subject request.
|
|
458
|
+
We will retain this information for as long as necessary to fulfill your request and to comply with our legal obligations.
|
|
459
|
+
For more information, please refer to our Privacy Policy.
|
|
460
|
+
</p>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
{/* Submit Button */}
|
|
464
|
+
<div className="mt-6">
|
|
465
|
+
<button
|
|
466
|
+
type="submit"
|
|
467
|
+
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}`}
|
|
468
|
+
>
|
|
469
|
+
{labels.submit || submitButtonText}
|
|
470
|
+
</button>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
</form>
|
|
474
|
+
</div>
|
|
475
|
+
);
|
|
476
|
+
};
|