@tantainnovative/ndpr-toolkit 1.0.5 → 1.0.6
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/README.md +447 -84
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +35 -0
- package/package.json +52 -70
- package/CHANGELOG.md +0 -16
- package/CNAME +0 -1
- package/CONTRIBUTING.md +0 -87
- package/RELEASE-NOTES-v1.0.0.md +0 -140
- package/RELEASE-NOTES-v1.0.1.md +0 -69
- package/SECURITY.md +0 -21
- package/components.json +0 -21
- package/eslint.config.mjs +0 -16
- package/next-env.d.ts +0 -5
- package/next.config.js +0 -15
- package/next.config.ts +0 -62
- package/packages/ndpr-toolkit/README.md +0 -467
- package/packages/ndpr-toolkit/dist/index.esm.js +0 -2
- package/packages/ndpr-toolkit/dist/index.esm.js.map +0 -1
- package/packages/ndpr-toolkit/dist/index.js +0 -2
- package/packages/ndpr-toolkit/dist/index.js.map +0 -1
- 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/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/public/NDPR TOOLKIT.svg +0 -1
- 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 +0 -1
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/ndpr-toolkit-logo.svg +0 -108
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- package/src/app/accessibility.css +0 -70
- 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 -193
- package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +0 -530
- package/src/components/dpia/DPIAQuestionnaire.tsx +0 -523
- 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 -361
- 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 -226
- 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 -70
- package/src/hooks/useLoadingState.ts +0 -85
- package/src/lib/consentService.ts +0 -144
- package/src/lib/dpiaQuestions.ts +0 -188
- package/src/lib/requestService.ts +0 -79
- 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 -77
- package/tailwind.config.ts +0 -65
- package/tsconfig.json +0 -41
- /package/{packages/ndpr-toolkit/dist → dist}/components/breach/BreachNotificationManager.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/breach/BreachReportForm.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/breach/BreachRiskAssessment.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/breach/RegulatoryReportGenerator.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/consent/ConsentBanner.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/consent/ConsentManager.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/consent/ConsentStorage.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/dpia/DPIAQuestionnaire.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/dpia/DPIAReport.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/dpia/StepIndicator.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/dsr/DSRDashboard.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/dsr/DSRRequestForm.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/dsr/DSRTracker.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/policy/PolicyExporter.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/policy/PolicyGenerator.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/components/policy/PolicyPreview.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/hooks/useBreach.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/hooks/useConsent.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/hooks/useDPIA.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/hooks/useDSR.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/hooks/usePrivacyPolicy.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/index.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/setupTests.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/types/breach.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/types/consent.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/types/dpia.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/types/dsr.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/types/privacy.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/utils/breach.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/utils/consent.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/utils/dpia.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/utils/dsr.d.ts +0 -0
- /package/{packages/ndpr-toolkit/dist → dist}/utils/privacy.d.ts +0 -0
|
@@ -1,530 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
4
|
-
import { RequestType } from "@/types";
|
|
5
|
-
import { Input } from "@/components/ui/Input";
|
|
6
|
-
import { TextArea } from "@/components/ui/TextArea";
|
|
7
|
-
import { Button } from "@/components/ui/Button";
|
|
8
|
-
import { FormField } from "@/components/ui/FormField";
|
|
9
|
-
import { Checkbox } from "@/components/ui/Checkbox";
|
|
10
|
-
import { LoadingButton } from "@/components/ui/Loading";
|
|
11
|
-
import { cn } from "@/lib/utils";
|
|
12
|
-
|
|
13
|
-
// Helper function to get icons for request types
|
|
14
|
-
const getRequestTypeIcon = (requestType: RequestType) => {
|
|
15
|
-
switch (requestType) {
|
|
16
|
-
case "access":
|
|
17
|
-
return (
|
|
18
|
-
<svg
|
|
19
|
-
className="w-5 h-5"
|
|
20
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
-
viewBox="0 0 20 20"
|
|
22
|
-
fill="currentColor"
|
|
23
|
-
>
|
|
24
|
-
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
|
25
|
-
<path
|
|
26
|
-
fillRule="evenodd"
|
|
27
|
-
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
|
28
|
-
clipRule="evenodd"
|
|
29
|
-
/>
|
|
30
|
-
</svg>
|
|
31
|
-
);
|
|
32
|
-
case "rectification":
|
|
33
|
-
return (
|
|
34
|
-
<svg
|
|
35
|
-
className="w-5 h-5"
|
|
36
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
-
viewBox="0 0 20 20"
|
|
38
|
-
fill="currentColor"
|
|
39
|
-
>
|
|
40
|
-
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
|
41
|
-
</svg>
|
|
42
|
-
);
|
|
43
|
-
case "erasure":
|
|
44
|
-
return (
|
|
45
|
-
<svg
|
|
46
|
-
className="w-5 h-5"
|
|
47
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
48
|
-
viewBox="0 0 20 20"
|
|
49
|
-
fill="currentColor"
|
|
50
|
-
>
|
|
51
|
-
<path
|
|
52
|
-
fillRule="evenodd"
|
|
53
|
-
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
|
54
|
-
clipRule="evenodd"
|
|
55
|
-
/>
|
|
56
|
-
</svg>
|
|
57
|
-
);
|
|
58
|
-
case "restriction":
|
|
59
|
-
return (
|
|
60
|
-
<svg
|
|
61
|
-
className="w-5 h-5"
|
|
62
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
63
|
-
viewBox="0 0 20 20"
|
|
64
|
-
fill="currentColor"
|
|
65
|
-
>
|
|
66
|
-
<path
|
|
67
|
-
fillRule="evenodd"
|
|
68
|
-
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
|
|
69
|
-
clipRule="evenodd"
|
|
70
|
-
/>
|
|
71
|
-
</svg>
|
|
72
|
-
);
|
|
73
|
-
case "portability":
|
|
74
|
-
return (
|
|
75
|
-
<svg
|
|
76
|
-
className="w-5 h-5"
|
|
77
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
78
|
-
viewBox="0 0 20 20"
|
|
79
|
-
fill="currentColor"
|
|
80
|
-
>
|
|
81
|
-
<path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z" />
|
|
82
|
-
<path
|
|
83
|
-
fillRule="evenodd"
|
|
84
|
-
d="M3 8h14v7a2 2 0 01-2 2H5a2 2 0 01-2-2V8zm5 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"
|
|
85
|
-
clipRule="evenodd"
|
|
86
|
-
/>
|
|
87
|
-
</svg>
|
|
88
|
-
);
|
|
89
|
-
case "objection":
|
|
90
|
-
return (
|
|
91
|
-
<svg
|
|
92
|
-
className="w-5 h-5"
|
|
93
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
94
|
-
viewBox="0 0 20 20"
|
|
95
|
-
fill="currentColor"
|
|
96
|
-
>
|
|
97
|
-
<path
|
|
98
|
-
fillRule="evenodd"
|
|
99
|
-
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
|
|
100
|
-
clipRule="evenodd"
|
|
101
|
-
/>
|
|
102
|
-
</svg>
|
|
103
|
-
);
|
|
104
|
-
default:
|
|
105
|
-
return (
|
|
106
|
-
<svg
|
|
107
|
-
className="w-5 h-5"
|
|
108
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
109
|
-
viewBox="0 0 20 20"
|
|
110
|
-
fill="currentColor"
|
|
111
|
-
>
|
|
112
|
-
<path
|
|
113
|
-
fillRule="evenodd"
|
|
114
|
-
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z"
|
|
115
|
-
clipRule="evenodd"
|
|
116
|
-
/>
|
|
117
|
-
</svg>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
interface DataSubjectRequestFormProps {
|
|
123
|
-
onSubmit: (data: {
|
|
124
|
-
requestType: RequestType;
|
|
125
|
-
name: string;
|
|
126
|
-
email: string;
|
|
127
|
-
details: string;
|
|
128
|
-
consent: boolean;
|
|
129
|
-
}) => void;
|
|
130
|
-
className?: string;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export default function DataSubjectRequestForm({
|
|
134
|
-
onSubmit,
|
|
135
|
-
className = "",
|
|
136
|
-
}: DataSubjectRequestFormProps) {
|
|
137
|
-
const [formData, setFormData] = useState({
|
|
138
|
-
requestType: "access" as RequestType,
|
|
139
|
-
name: "",
|
|
140
|
-
email: "",
|
|
141
|
-
details: "",
|
|
142
|
-
consent: false,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
146
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
147
|
-
|
|
148
|
-
const requestTypeOptions: {
|
|
149
|
-
value: RequestType;
|
|
150
|
-
label: string;
|
|
151
|
-
description: string;
|
|
152
|
-
}[] = [
|
|
153
|
-
{
|
|
154
|
-
value: "access",
|
|
155
|
-
label: "Access to Personal Data",
|
|
156
|
-
description: "Request a copy of your personal data that we process",
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
value: "rectification",
|
|
160
|
-
label: "Rectification of Data",
|
|
161
|
-
description: "Request correction of inaccurate personal data",
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
value: "erasure",
|
|
165
|
-
label: "Erasure of Data",
|
|
166
|
-
description:
|
|
167
|
-
'Request deletion of your personal data ("right to be forgotten")',
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
value: "restriction",
|
|
171
|
-
label: "Restriction of Processing",
|
|
172
|
-
description: "Request to restrict how we process your data",
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
value: "portability",
|
|
176
|
-
label: "Data Portability",
|
|
177
|
-
description:
|
|
178
|
-
"Request to receive your data in a structured, machine-readable format",
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
value: "objection",
|
|
182
|
-
label: "Object to Processing",
|
|
183
|
-
description: "Object to processing of your personal data",
|
|
184
|
-
},
|
|
185
|
-
];
|
|
186
|
-
|
|
187
|
-
const handleChange = (
|
|
188
|
-
e: React.ChangeEvent<
|
|
189
|
-
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
190
|
-
>,
|
|
191
|
-
) => {
|
|
192
|
-
const { name, value } = e.target;
|
|
193
|
-
setFormData((prev) => ({
|
|
194
|
-
...prev,
|
|
195
|
-
[name]: value,
|
|
196
|
-
}));
|
|
197
|
-
|
|
198
|
-
// Clear error when field is edited
|
|
199
|
-
if (errors[name]) {
|
|
200
|
-
setErrors((prev) => {
|
|
201
|
-
const newErrors = { ...prev };
|
|
202
|
-
delete newErrors[name];
|
|
203
|
-
return newErrors;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const validateForm = () => {
|
|
209
|
-
const newErrors: Record<string, string> = {};
|
|
210
|
-
|
|
211
|
-
if (!formData.name.trim()) {
|
|
212
|
-
newErrors.name = "Name is required";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (!formData.email.trim()) {
|
|
216
|
-
newErrors.email = "Email is required";
|
|
217
|
-
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
218
|
-
newErrors.email = "Please enter a valid email address";
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (!formData.details.trim()) {
|
|
222
|
-
newErrors.details = "Please provide details about your request";
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (!formData.consent) {
|
|
226
|
-
newErrors.consent =
|
|
227
|
-
"You must consent to the processing of your data to submit this request";
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
setErrors(newErrors);
|
|
231
|
-
return Object.keys(newErrors).length === 0;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
235
|
-
e.preventDefault();
|
|
236
|
-
|
|
237
|
-
if (!validateForm()) return;
|
|
238
|
-
|
|
239
|
-
setIsSubmitting(true);
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
// In a real implementation, this would call an API
|
|
243
|
-
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API call
|
|
244
|
-
|
|
245
|
-
onSubmit({
|
|
246
|
-
requestType: formData.requestType,
|
|
247
|
-
name: formData.name,
|
|
248
|
-
email: formData.email,
|
|
249
|
-
details: formData.details,
|
|
250
|
-
consent: formData.consent,
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Reset form after successful submission
|
|
254
|
-
setFormData({
|
|
255
|
-
requestType: "access",
|
|
256
|
-
name: "",
|
|
257
|
-
email: "",
|
|
258
|
-
details: "",
|
|
259
|
-
consent: false,
|
|
260
|
-
});
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.error("Error submitting request:", error);
|
|
263
|
-
setErrors({
|
|
264
|
-
submit:
|
|
265
|
-
"An error occurred while submitting your request. Please try again.",
|
|
266
|
-
});
|
|
267
|
-
} finally {
|
|
268
|
-
setIsSubmitting(false);
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// Track form completion percentage for progress indicator
|
|
273
|
-
const [completionPercentage, setCompletionPercentage] = useState(0);
|
|
274
|
-
|
|
275
|
-
// Calculate form completion percentage
|
|
276
|
-
useEffect(() => {
|
|
277
|
-
let filledFields = 0;
|
|
278
|
-
const totalFields = 3; // name, email, details
|
|
279
|
-
|
|
280
|
-
if (formData.name.trim()) filledFields++;
|
|
281
|
-
if (formData.email.trim()) filledFields++;
|
|
282
|
-
if (formData.details.trim()) filledFields++;
|
|
283
|
-
|
|
284
|
-
setCompletionPercentage(Math.round((filledFields / totalFields) * 100));
|
|
285
|
-
}, [formData]);
|
|
286
|
-
|
|
287
|
-
return (
|
|
288
|
-
<div
|
|
289
|
-
className={`bg-white dark:bg-gray-800 shadow-lg rounded-lg overflow-hidden ${className}`}
|
|
290
|
-
>
|
|
291
|
-
{/* Header with progress indicator */}
|
|
292
|
-
<div className="bg-gradient-to-r from-blue-600 to-indigo-700 p-6 text-white relative">
|
|
293
|
-
<h2 className="text-xl font-bold mb-2">
|
|
294
|
-
Submit a Data Subject Request
|
|
295
|
-
</h2>
|
|
296
|
-
<p className="text-blue-100 text-sm mb-4">
|
|
297
|
-
Exercise your rights under the Nigeria Data Protection Regulation
|
|
298
|
-
</p>
|
|
299
|
-
|
|
300
|
-
{/* Progress bar */}
|
|
301
|
-
<div className="w-full h-2 bg-blue-800 rounded-full overflow-hidden mt-2">
|
|
302
|
-
<div
|
|
303
|
-
className="h-full bg-green-400 transition-all duration-300 ease-out"
|
|
304
|
-
style={{ width: `${completionPercentage}%` }}
|
|
305
|
-
></div>
|
|
306
|
-
</div>
|
|
307
|
-
<div className="flex justify-between text-xs text-blue-200 mt-1">
|
|
308
|
-
<span>Form Progress</span>
|
|
309
|
-
<span>{completionPercentage}% Complete</span>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
|
|
313
|
-
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
|
314
|
-
{/* Request Type Selection with Icons */}
|
|
315
|
-
<div className="mb-8">
|
|
316
|
-
<label
|
|
317
|
-
htmlFor="requestType"
|
|
318
|
-
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
|
|
319
|
-
>
|
|
320
|
-
Request Type
|
|
321
|
-
</label>
|
|
322
|
-
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mb-3">
|
|
323
|
-
{requestTypeOptions.map((option) => (
|
|
324
|
-
<div
|
|
325
|
-
key={option.value}
|
|
326
|
-
onClick={() =>
|
|
327
|
-
setFormData({ ...formData, requestType: option.value })
|
|
328
|
-
}
|
|
329
|
-
className={cn(
|
|
330
|
-
"cursor-pointer border rounded-lg p-3 transition-all",
|
|
331
|
-
formData.requestType === option.value
|
|
332
|
-
? "bg-blue-50 border-blue-500 dark:bg-blue-900/30 dark:border-blue-400"
|
|
333
|
-
: "border-gray-200 hover:border-gray-300 dark:border-gray-700 dark:hover:border-gray-600",
|
|
334
|
-
)}
|
|
335
|
-
>
|
|
336
|
-
<div className="flex flex-col items-center text-center">
|
|
337
|
-
<div
|
|
338
|
-
className={cn(
|
|
339
|
-
"w-10 h-10 flex items-center justify-center rounded-full mb-2",
|
|
340
|
-
formData.requestType === option.value
|
|
341
|
-
? "bg-blue-100 text-blue-600 dark:bg-blue-800 dark:text-blue-300"
|
|
342
|
-
: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400",
|
|
343
|
-
)}
|
|
344
|
-
>
|
|
345
|
-
{getRequestTypeIcon(option.value)}
|
|
346
|
-
</div>
|
|
347
|
-
<span className="text-sm font-medium">{option.label}</span>
|
|
348
|
-
</div>
|
|
349
|
-
</div>
|
|
350
|
-
))}
|
|
351
|
-
</div>
|
|
352
|
-
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50 p-3 rounded-lg border border-gray-100 dark:border-gray-700">
|
|
353
|
-
<span className="font-medium block mb-1">About this request:</span>
|
|
354
|
-
{
|
|
355
|
-
requestTypeOptions.find(
|
|
356
|
-
(option) => option.value === formData.requestType,
|
|
357
|
-
)?.description
|
|
358
|
-
}
|
|
359
|
-
</p>
|
|
360
|
-
</div>
|
|
361
|
-
|
|
362
|
-
{/* Personal Information Section */}
|
|
363
|
-
<div className="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg border border-gray-100 dark:border-gray-700 mb-6">
|
|
364
|
-
<h3 className="text-md font-medium text-gray-900 dark:text-white mb-4">
|
|
365
|
-
Personal Information
|
|
366
|
-
</h3>
|
|
367
|
-
|
|
368
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
369
|
-
<FormField id="name" label="Full Name" required error={errors.name}>
|
|
370
|
-
<div className="relative">
|
|
371
|
-
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
372
|
-
<svg
|
|
373
|
-
className="h-5 w-5 text-gray-400"
|
|
374
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
375
|
-
viewBox="0 0 20 20"
|
|
376
|
-
fill="currentColor"
|
|
377
|
-
>
|
|
378
|
-
<path
|
|
379
|
-
fillRule="evenodd"
|
|
380
|
-
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
|
|
381
|
-
clipRule="evenodd"
|
|
382
|
-
/>
|
|
383
|
-
</svg>
|
|
384
|
-
</div>
|
|
385
|
-
<Input
|
|
386
|
-
id="name"
|
|
387
|
-
name="name"
|
|
388
|
-
value={formData.name}
|
|
389
|
-
onChange={handleChange}
|
|
390
|
-
className={cn(
|
|
391
|
-
"pl-10",
|
|
392
|
-
errors.name &&
|
|
393
|
-
"border-red-300 focus:border-red-500 focus:ring-red-500",
|
|
394
|
-
)}
|
|
395
|
-
/>
|
|
396
|
-
</div>
|
|
397
|
-
</FormField>
|
|
398
|
-
|
|
399
|
-
<FormField
|
|
400
|
-
id="email"
|
|
401
|
-
label="Email Address"
|
|
402
|
-
required
|
|
403
|
-
error={errors.email}
|
|
404
|
-
>
|
|
405
|
-
<div className="relative">
|
|
406
|
-
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
407
|
-
<svg
|
|
408
|
-
className="h-5 w-5 text-gray-400"
|
|
409
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
410
|
-
viewBox="0 0 20 20"
|
|
411
|
-
fill="currentColor"
|
|
412
|
-
>
|
|
413
|
-
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
|
414
|
-
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
|
415
|
-
</svg>
|
|
416
|
-
</div>
|
|
417
|
-
<Input
|
|
418
|
-
type="email"
|
|
419
|
-
id="email"
|
|
420
|
-
name="email"
|
|
421
|
-
value={formData.email}
|
|
422
|
-
onChange={handleChange}
|
|
423
|
-
className={cn(
|
|
424
|
-
"pl-10",
|
|
425
|
-
errors.email &&
|
|
426
|
-
"border-red-300 focus:border-red-500 focus:ring-red-500",
|
|
427
|
-
)}
|
|
428
|
-
/>
|
|
429
|
-
</div>
|
|
430
|
-
</FormField>
|
|
431
|
-
</div>
|
|
432
|
-
</div>
|
|
433
|
-
|
|
434
|
-
{/* Request Details Section */}
|
|
435
|
-
<FormField
|
|
436
|
-
id="details"
|
|
437
|
-
label="Request Details"
|
|
438
|
-
required
|
|
439
|
-
error={errors.details}
|
|
440
|
-
description="Your request will be processed in accordance with the Nigeria Data Protection Regulation (NDPR) and Data Protection Act (DPA)."
|
|
441
|
-
>
|
|
442
|
-
<div className="relative">
|
|
443
|
-
<TextArea
|
|
444
|
-
id="details"
|
|
445
|
-
name="details"
|
|
446
|
-
rows={5}
|
|
447
|
-
value={formData.details}
|
|
448
|
-
onChange={handleChange}
|
|
449
|
-
className={cn(
|
|
450
|
-
errors.details &&
|
|
451
|
-
"border-red-300 focus:border-red-500 focus:ring-red-500",
|
|
452
|
-
)}
|
|
453
|
-
placeholder="Please provide specific details about your request. For example, what personal data you would like to access, what corrections you need to make, etc."
|
|
454
|
-
/>
|
|
455
|
-
<div className="absolute bottom-2 right-2 text-xs text-gray-400">
|
|
456
|
-
{formData.details.length} characters
|
|
457
|
-
</div>
|
|
458
|
-
</div>
|
|
459
|
-
</FormField>
|
|
460
|
-
|
|
461
|
-
{/* Consent Checkbox */}
|
|
462
|
-
<div className="mt-4">
|
|
463
|
-
<Checkbox
|
|
464
|
-
id="consent"
|
|
465
|
-
name="consent"
|
|
466
|
-
checked={formData.consent}
|
|
467
|
-
onChange={(e) =>
|
|
468
|
-
setFormData({ ...formData, consent: e.target.checked })
|
|
469
|
-
}
|
|
470
|
-
label="I consent to the processing of my personal data"
|
|
471
|
-
description="By submitting this form, you consent to our processing of your personal data for the purpose of handling your request."
|
|
472
|
-
/>
|
|
473
|
-
{errors.consent && (
|
|
474
|
-
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
|
|
475
|
-
{errors.consent}
|
|
476
|
-
</p>
|
|
477
|
-
)}
|
|
478
|
-
</div>
|
|
479
|
-
|
|
480
|
-
{errors.submit && (
|
|
481
|
-
<div className="rounded-md bg-red-50 dark:bg-red-900/20 p-4 flex items-start">
|
|
482
|
-
<svg
|
|
483
|
-
className="h-5 w-5 text-red-400 mt-0.5 mr-2"
|
|
484
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
485
|
-
viewBox="0 0 20 20"
|
|
486
|
-
fill="currentColor"
|
|
487
|
-
>
|
|
488
|
-
<path
|
|
489
|
-
fillRule="evenodd"
|
|
490
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
491
|
-
clipRule="evenodd"
|
|
492
|
-
/>
|
|
493
|
-
</svg>
|
|
494
|
-
<p className="text-sm text-red-700 dark:text-red-400">
|
|
495
|
-
{errors.submit}
|
|
496
|
-
</p>
|
|
497
|
-
</div>
|
|
498
|
-
)}
|
|
499
|
-
|
|
500
|
-
<div className="flex justify-end pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
501
|
-
<Button
|
|
502
|
-
type="button"
|
|
503
|
-
variant="secondary"
|
|
504
|
-
onClick={() => {
|
|
505
|
-
setFormData({
|
|
506
|
-
requestType: "access",
|
|
507
|
-
name: "",
|
|
508
|
-
email: "",
|
|
509
|
-
details: "",
|
|
510
|
-
consent: false,
|
|
511
|
-
});
|
|
512
|
-
setErrors({});
|
|
513
|
-
}}
|
|
514
|
-
className="mr-3"
|
|
515
|
-
>
|
|
516
|
-
Reset Form
|
|
517
|
-
</Button>
|
|
518
|
-
<LoadingButton
|
|
519
|
-
type="submit"
|
|
520
|
-
loading={isSubmitting}
|
|
521
|
-
loadingText="Processing..."
|
|
522
|
-
className="px-6 py-2 bg-blue-600 text-white hover:bg-blue-700 rounded-md font-medium"
|
|
523
|
-
>
|
|
524
|
-
Submit Request
|
|
525
|
-
</LoadingButton>
|
|
526
|
-
</div>
|
|
527
|
-
</form>
|
|
528
|
-
</div>
|
|
529
|
-
);
|
|
530
|
-
}
|