@tantainnovative/ndpr-toolkit 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/next-env.d.ts +5 -0
- package/package.json +1 -1
- package/packages/ndpr-toolkit/dist/components/breach/BreachNotificationManager.d.ts +62 -0
- package/packages/ndpr-toolkit/dist/components/breach/BreachReportForm.d.ts +66 -0
- package/packages/ndpr-toolkit/dist/components/breach/BreachRiskAssessment.d.ts +50 -0
- package/packages/ndpr-toolkit/dist/components/breach/RegulatoryReportGenerator.d.ts +94 -0
- package/packages/ndpr-toolkit/dist/components/consent/ConsentBanner.d.ts +79 -0
- package/packages/ndpr-toolkit/dist/components/consent/ConsentManager.d.ts +73 -0
- package/packages/ndpr-toolkit/dist/components/consent/ConsentStorage.d.ts +41 -0
- package/packages/ndpr-toolkit/dist/components/dpia/DPIAQuestionnaire.d.ts +70 -0
- package/packages/ndpr-toolkit/dist/components/dpia/DPIAReport.d.ts +40 -0
- package/packages/ndpr-toolkit/dist/components/dpia/StepIndicator.d.ts +64 -0
- package/packages/ndpr-toolkit/dist/components/dsr/DSRDashboard.d.ts +58 -0
- package/packages/ndpr-toolkit/dist/components/dsr/DSRRequestForm.d.ts +74 -0
- package/packages/ndpr-toolkit/dist/components/dsr/DSRTracker.d.ts +56 -0
- package/packages/ndpr-toolkit/dist/components/policy/PolicyExporter.d.ts +65 -0
- package/packages/ndpr-toolkit/dist/components/policy/PolicyGenerator.d.ts +54 -0
- package/packages/ndpr-toolkit/dist/components/policy/PolicyPreview.d.ts +71 -0
- package/packages/ndpr-toolkit/dist/hooks/useBreach.d.ts +97 -0
- package/packages/ndpr-toolkit/dist/hooks/useConsent.d.ts +63 -0
- package/packages/ndpr-toolkit/dist/hooks/useDPIA.d.ts +92 -0
- package/packages/ndpr-toolkit/dist/hooks/useDSR.d.ts +72 -0
- package/packages/ndpr-toolkit/dist/hooks/usePrivacyPolicy.d.ts +87 -0
- package/packages/ndpr-toolkit/dist/index.d.ts +31 -0
- package/packages/ndpr-toolkit/dist/index.esm.js +2 -0
- package/packages/ndpr-toolkit/dist/index.esm.js.map +1 -0
- package/packages/ndpr-toolkit/dist/index.js +2 -0
- package/packages/ndpr-toolkit/dist/index.js.map +1 -0
- package/packages/ndpr-toolkit/dist/setupTests.d.ts +2 -0
- package/packages/ndpr-toolkit/dist/types/breach.d.ts +239 -0
- package/packages/ndpr-toolkit/dist/types/consent.d.ts +95 -0
- package/packages/ndpr-toolkit/dist/types/dpia.d.ts +196 -0
- package/packages/ndpr-toolkit/dist/types/dsr.d.ts +162 -0
- package/packages/ndpr-toolkit/dist/types/privacy.d.ts +204 -0
- package/packages/ndpr-toolkit/dist/utils/breach.d.ts +14 -0
- package/packages/ndpr-toolkit/dist/utils/consent.d.ts +10 -0
- package/packages/ndpr-toolkit/dist/utils/dpia.d.ts +12 -0
- package/packages/ndpr-toolkit/dist/utils/dsr.d.ts +11 -0
- package/packages/ndpr-toolkit/dist/utils/privacy.d.ts +12 -0
- package/src/components/consent/ConsentBanner.tsx +82 -48
- package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +240 -129
- package/src/components/dpia/DPIAQuestionnaire.tsx +162 -122
- package/src/components/privacy-policy/PolicyGenerator.tsx +5 -5
- package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +103 -77
- package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +117 -63
- package/src/hooks/useConsent.ts +16 -10
- package/src/lib/consentService.ts +44 -37
- package/src/lib/dpiaQuestions.ts +139 -99
- package/src/lib/requestService.ts +21 -17
- package/src/types/index.ts +13 -8
- package/.claude/settings.local.json +0 -20
- package/.eslintrc.json +0 -10
- package/.github/workflows/ci.yml +0 -36
- package/.github/workflows/nextjs.yml +0 -104
- package/.husky/commit-msg +0 -4
- package/.husky/pre-commit +0 -4
- package/.lintstagedrc.js +0 -4
- package/.nvmrc +0 -1
- package/.versionrc +0 -17
- package/CLAUDE.md +0 -90
- package/commitlint.config.js +0 -36
- package/jest.config.js +0 -31
- package/jest.setup.js +0 -15
- package/packages/ndpr-toolkit/jest.config.js +0 -23
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +0 -119
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +0 -122
- package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +0 -270
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +0 -199
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +0 -224
- package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +0 -104
- package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +0 -161
- package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +0 -330
- package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +0 -149
- package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +0 -88
- package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +0 -160
- package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +0 -110
- package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +0 -97
- package/src/__tests__/example.test.ts +0 -13
- package/src/__tests__/requestService.test.ts +0 -57
- package/src/app/docs/components/DocLayout.tsx +0 -267
- package/src/app/docs/components/breach-notification/page.tsx +0 -797
- package/src/app/docs/components/consent-management/page.tsx +0 -576
- package/src/app/docs/components/data-subject-rights/page.tsx +0 -511
- package/src/app/docs/components/dpia-questionnaire/layout.tsx +0 -15
- package/src/app/docs/components/dpia-questionnaire/metadata.ts +0 -31
- package/src/app/docs/components/dpia-questionnaire/page.tsx +0 -666
- package/src/app/docs/components/hooks/page.tsx +0 -305
- package/src/app/docs/components/page.tsx +0 -84
- package/src/app/docs/components/privacy-policy-generator/page.tsx +0 -634
- package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +0 -123
- package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +0 -328
- package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +0 -28
- package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +0 -91
- package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +0 -118
- package/src/app/docs/guides/breach-notification-process/page.tsx +0 -39
- package/src/app/docs/guides/conducting-dpia/page.tsx +0 -593
- package/src/app/docs/guides/data-subject-requests/page.tsx +0 -666
- package/src/app/docs/guides/managing-consent/page.tsx +0 -738
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +0 -296
- package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +0 -145
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +0 -33
- package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +0 -99
- package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +0 -159
- package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +0 -38
- package/src/app/docs/guides/page.tsx +0 -67
- package/src/app/docs/layout.tsx +0 -15
- package/src/app/docs/metadata.ts +0 -31
- package/src/app/docs/page.tsx +0 -572
- package/src/components/docs/DocLayout.tsx +0 -289
- package/src/components/docs/index.ts +0 -2
|
@@ -1,60 +1,119 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from
|
|
4
|
-
import { RequestType } from
|
|
5
|
-
import { Input } from
|
|
6
|
-
import { TextArea } from
|
|
7
|
-
import { Button } from
|
|
8
|
-
import { FormField } from
|
|
9
|
-
import { Checkbox } from
|
|
10
|
-
import { LoadingButton } from
|
|
11
|
-
import { cn } from
|
|
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
12
|
|
|
13
13
|
// Helper function to get icons for request types
|
|
14
14
|
const getRequestTypeIcon = (requestType: RequestType) => {
|
|
15
15
|
switch (requestType) {
|
|
16
|
-
case
|
|
16
|
+
case "access":
|
|
17
17
|
return (
|
|
18
|
-
<svg
|
|
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
|
+
>
|
|
19
24
|
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
|
20
|
-
<path
|
|
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
|
+
/>
|
|
21
30
|
</svg>
|
|
22
31
|
);
|
|
23
|
-
case
|
|
32
|
+
case "rectification":
|
|
24
33
|
return (
|
|
25
|
-
<svg
|
|
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
|
+
>
|
|
26
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" />
|
|
27
41
|
</svg>
|
|
28
42
|
);
|
|
29
|
-
case
|
|
43
|
+
case "erasure":
|
|
30
44
|
return (
|
|
31
|
-
<svg
|
|
32
|
-
|
|
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
|
+
/>
|
|
33
56
|
</svg>
|
|
34
57
|
);
|
|
35
|
-
case
|
|
58
|
+
case "restriction":
|
|
36
59
|
return (
|
|
37
|
-
<svg
|
|
38
|
-
|
|
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
|
+
/>
|
|
39
71
|
</svg>
|
|
40
72
|
);
|
|
41
|
-
case
|
|
73
|
+
case "portability":
|
|
42
74
|
return (
|
|
43
|
-
<svg
|
|
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
|
+
>
|
|
44
81
|
<path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z" />
|
|
45
|
-
<path
|
|
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
|
+
/>
|
|
46
87
|
</svg>
|
|
47
88
|
);
|
|
48
|
-
case
|
|
89
|
+
case "objection":
|
|
49
90
|
return (
|
|
50
|
-
<svg
|
|
51
|
-
|
|
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
|
+
/>
|
|
52
102
|
</svg>
|
|
53
103
|
);
|
|
54
104
|
default:
|
|
55
105
|
return (
|
|
56
|
-
<svg
|
|
57
|
-
|
|
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
|
+
/>
|
|
58
117
|
</svg>
|
|
59
118
|
);
|
|
60
119
|
}
|
|
@@ -73,61 +132,69 @@ interface DataSubjectRequestFormProps {
|
|
|
73
132
|
|
|
74
133
|
export default function DataSubjectRequestForm({
|
|
75
134
|
onSubmit,
|
|
76
|
-
className =
|
|
135
|
+
className = "",
|
|
77
136
|
}: DataSubjectRequestFormProps) {
|
|
78
137
|
const [formData, setFormData] = useState({
|
|
79
|
-
requestType:
|
|
80
|
-
name:
|
|
81
|
-
email:
|
|
82
|
-
details:
|
|
138
|
+
requestType: "access" as RequestType,
|
|
139
|
+
name: "",
|
|
140
|
+
email: "",
|
|
141
|
+
details: "",
|
|
83
142
|
consent: false,
|
|
84
143
|
});
|
|
85
|
-
|
|
144
|
+
|
|
86
145
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
87
146
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
88
147
|
|
|
89
|
-
const requestTypeOptions: {
|
|
148
|
+
const requestTypeOptions: {
|
|
149
|
+
value: RequestType;
|
|
150
|
+
label: string;
|
|
151
|
+
description: string;
|
|
152
|
+
}[] = [
|
|
90
153
|
{
|
|
91
|
-
value:
|
|
92
|
-
label:
|
|
93
|
-
description:
|
|
154
|
+
value: "access",
|
|
155
|
+
label: "Access to Personal Data",
|
|
156
|
+
description: "Request a copy of your personal data that we process",
|
|
94
157
|
},
|
|
95
158
|
{
|
|
96
|
-
value:
|
|
97
|
-
label:
|
|
98
|
-
description:
|
|
159
|
+
value: "rectification",
|
|
160
|
+
label: "Rectification of Data",
|
|
161
|
+
description: "Request correction of inaccurate personal data",
|
|
99
162
|
},
|
|
100
163
|
{
|
|
101
|
-
value:
|
|
102
|
-
label:
|
|
103
|
-
description:
|
|
164
|
+
value: "erasure",
|
|
165
|
+
label: "Erasure of Data",
|
|
166
|
+
description:
|
|
167
|
+
'Request deletion of your personal data ("right to be forgotten")',
|
|
104
168
|
},
|
|
105
169
|
{
|
|
106
|
-
value:
|
|
107
|
-
label:
|
|
108
|
-
description:
|
|
170
|
+
value: "restriction",
|
|
171
|
+
label: "Restriction of Processing",
|
|
172
|
+
description: "Request to restrict how we process your data",
|
|
109
173
|
},
|
|
110
174
|
{
|
|
111
|
-
value:
|
|
112
|
-
label:
|
|
113
|
-
description:
|
|
175
|
+
value: "portability",
|
|
176
|
+
label: "Data Portability",
|
|
177
|
+
description:
|
|
178
|
+
"Request to receive your data in a structured, machine-readable format",
|
|
114
179
|
},
|
|
115
180
|
{
|
|
116
|
-
value:
|
|
117
|
-
label:
|
|
118
|
-
description:
|
|
181
|
+
value: "objection",
|
|
182
|
+
label: "Object to Processing",
|
|
183
|
+
description: "Object to processing of your personal data",
|
|
119
184
|
},
|
|
120
185
|
];
|
|
121
186
|
|
|
122
187
|
const handleChange = (
|
|
123
|
-
e: React.ChangeEvent<
|
|
188
|
+
e: React.ChangeEvent<
|
|
189
|
+
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
190
|
+
>,
|
|
124
191
|
) => {
|
|
125
192
|
const { name, value } = e.target;
|
|
126
193
|
setFormData((prev) => ({
|
|
127
194
|
...prev,
|
|
128
195
|
[name]: value,
|
|
129
196
|
}));
|
|
130
|
-
|
|
197
|
+
|
|
131
198
|
// Clear error when field is edited
|
|
132
199
|
if (errors[name]) {
|
|
133
200
|
setErrors((prev) => {
|
|
@@ -140,40 +207,41 @@ export default function DataSubjectRequestForm({
|
|
|
140
207
|
|
|
141
208
|
const validateForm = () => {
|
|
142
209
|
const newErrors: Record<string, string> = {};
|
|
143
|
-
|
|
210
|
+
|
|
144
211
|
if (!formData.name.trim()) {
|
|
145
|
-
newErrors.name =
|
|
212
|
+
newErrors.name = "Name is required";
|
|
146
213
|
}
|
|
147
|
-
|
|
214
|
+
|
|
148
215
|
if (!formData.email.trim()) {
|
|
149
|
-
newErrors.email =
|
|
216
|
+
newErrors.email = "Email is required";
|
|
150
217
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
151
|
-
newErrors.email =
|
|
218
|
+
newErrors.email = "Please enter a valid email address";
|
|
152
219
|
}
|
|
153
|
-
|
|
220
|
+
|
|
154
221
|
if (!formData.details.trim()) {
|
|
155
|
-
newErrors.details =
|
|
222
|
+
newErrors.details = "Please provide details about your request";
|
|
156
223
|
}
|
|
157
|
-
|
|
224
|
+
|
|
158
225
|
if (!formData.consent) {
|
|
159
|
-
newErrors.consent =
|
|
226
|
+
newErrors.consent =
|
|
227
|
+
"You must consent to the processing of your data to submit this request";
|
|
160
228
|
}
|
|
161
|
-
|
|
229
|
+
|
|
162
230
|
setErrors(newErrors);
|
|
163
231
|
return Object.keys(newErrors).length === 0;
|
|
164
232
|
};
|
|
165
233
|
|
|
166
234
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
167
235
|
e.preventDefault();
|
|
168
|
-
|
|
236
|
+
|
|
169
237
|
if (!validateForm()) return;
|
|
170
|
-
|
|
238
|
+
|
|
171
239
|
setIsSubmitting(true);
|
|
172
|
-
|
|
240
|
+
|
|
173
241
|
try {
|
|
174
242
|
// In a real implementation, this would call an API
|
|
175
243
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API call
|
|
176
|
-
|
|
244
|
+
|
|
177
245
|
onSubmit({
|
|
178
246
|
requestType: formData.requestType,
|
|
179
247
|
name: formData.name,
|
|
@@ -181,19 +249,20 @@ export default function DataSubjectRequestForm({
|
|
|
181
249
|
details: formData.details,
|
|
182
250
|
consent: formData.consent,
|
|
183
251
|
});
|
|
184
|
-
|
|
252
|
+
|
|
185
253
|
// Reset form after successful submission
|
|
186
254
|
setFormData({
|
|
187
|
-
requestType:
|
|
188
|
-
name:
|
|
189
|
-
email:
|
|
190
|
-
details:
|
|
255
|
+
requestType: "access",
|
|
256
|
+
name: "",
|
|
257
|
+
email: "",
|
|
258
|
+
details: "",
|
|
191
259
|
consent: false,
|
|
192
260
|
});
|
|
193
261
|
} catch (error) {
|
|
194
|
-
console.error(
|
|
262
|
+
console.error("Error submitting request:", error);
|
|
195
263
|
setErrors({
|
|
196
|
-
submit:
|
|
264
|
+
submit:
|
|
265
|
+
"An error occurred while submitting your request. Please try again.",
|
|
197
266
|
});
|
|
198
267
|
} finally {
|
|
199
268
|
setIsSubmitting(false);
|
|
@@ -202,21 +271,23 @@ export default function DataSubjectRequestForm({
|
|
|
202
271
|
|
|
203
272
|
// Track form completion percentage for progress indicator
|
|
204
273
|
const [completionPercentage, setCompletionPercentage] = useState(0);
|
|
205
|
-
|
|
274
|
+
|
|
206
275
|
// Calculate form completion percentage
|
|
207
276
|
useEffect(() => {
|
|
208
277
|
let filledFields = 0;
|
|
209
278
|
const totalFields = 3; // name, email, details
|
|
210
|
-
|
|
279
|
+
|
|
211
280
|
if (formData.name.trim()) filledFields++;
|
|
212
281
|
if (formData.email.trim()) filledFields++;
|
|
213
282
|
if (formData.details.trim()) filledFields++;
|
|
214
|
-
|
|
283
|
+
|
|
215
284
|
setCompletionPercentage(Math.round((filledFields / totalFields) * 100));
|
|
216
285
|
}, [formData]);
|
|
217
|
-
|
|
286
|
+
|
|
218
287
|
return (
|
|
219
|
-
<div
|
|
288
|
+
<div
|
|
289
|
+
className={`bg-white dark:bg-gray-800 shadow-lg rounded-lg overflow-hidden ${className}`}
|
|
290
|
+
>
|
|
220
291
|
{/* Header with progress indicator */}
|
|
221
292
|
<div className="bg-gradient-to-r from-blue-600 to-indigo-700 p-6 text-white relative">
|
|
222
293
|
<h2 className="text-xl font-bold mb-2">
|
|
@@ -225,10 +296,10 @@ export default function DataSubjectRequestForm({
|
|
|
225
296
|
<p className="text-blue-100 text-sm mb-4">
|
|
226
297
|
Exercise your rights under the Nigeria Data Protection Regulation
|
|
227
298
|
</p>
|
|
228
|
-
|
|
299
|
+
|
|
229
300
|
{/* Progress bar */}
|
|
230
301
|
<div className="w-full h-2 bg-blue-800 rounded-full overflow-hidden mt-2">
|
|
231
|
-
<div
|
|
302
|
+
<div
|
|
232
303
|
className="h-full bg-green-400 transition-all duration-300 ease-out"
|
|
233
304
|
style={{ width: `${completionPercentage}%` }}
|
|
234
305
|
></div>
|
|
@@ -238,32 +309,39 @@ export default function DataSubjectRequestForm({
|
|
|
238
309
|
<span>{completionPercentage}% Complete</span>
|
|
239
310
|
</div>
|
|
240
311
|
</div>
|
|
241
|
-
|
|
312
|
+
|
|
242
313
|
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
|
243
314
|
{/* Request Type Selection with Icons */}
|
|
244
315
|
<div className="mb-8">
|
|
245
|
-
<label
|
|
316
|
+
<label
|
|
317
|
+
htmlFor="requestType"
|
|
318
|
+
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
|
|
319
|
+
>
|
|
246
320
|
Request Type
|
|
247
321
|
</label>
|
|
248
322
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mb-3">
|
|
249
323
|
{requestTypeOptions.map((option) => (
|
|
250
|
-
<div
|
|
324
|
+
<div
|
|
251
325
|
key={option.value}
|
|
252
|
-
onClick={() =>
|
|
326
|
+
onClick={() =>
|
|
327
|
+
setFormData({ ...formData, requestType: option.value })
|
|
328
|
+
}
|
|
253
329
|
className={cn(
|
|
254
330
|
"cursor-pointer border rounded-lg p-3 transition-all",
|
|
255
|
-
formData.requestType === option.value
|
|
256
|
-
? "bg-blue-50 border-blue-500 dark:bg-blue-900/30 dark:border-blue-400"
|
|
257
|
-
: "border-gray-200 hover:border-gray-300 dark:border-gray-700 dark:hover:border-gray-600"
|
|
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",
|
|
258
334
|
)}
|
|
259
335
|
>
|
|
260
336
|
<div className="flex flex-col items-center text-center">
|
|
261
|
-
<div
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
>
|
|
267
345
|
{getRequestTypeIcon(option.value)}
|
|
268
346
|
</div>
|
|
269
347
|
<span className="text-sm font-medium">{option.label}</span>
|
|
@@ -273,25 +351,35 @@ export default function DataSubjectRequestForm({
|
|
|
273
351
|
</div>
|
|
274
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">
|
|
275
353
|
<span className="font-medium block mb-1">About this request:</span>
|
|
276
|
-
{
|
|
354
|
+
{
|
|
355
|
+
requestTypeOptions.find(
|
|
356
|
+
(option) => option.value === formData.requestType,
|
|
357
|
+
)?.description
|
|
358
|
+
}
|
|
277
359
|
</p>
|
|
278
360
|
</div>
|
|
279
|
-
|
|
361
|
+
|
|
280
362
|
{/* Personal Information Section */}
|
|
281
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">
|
|
282
|
-
<h3 className="text-md font-medium text-gray-900 dark:text-white mb-4">
|
|
283
|
-
|
|
364
|
+
<h3 className="text-md font-medium text-gray-900 dark:text-white mb-4">
|
|
365
|
+
Personal Information
|
|
366
|
+
</h3>
|
|
367
|
+
|
|
284
368
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
285
|
-
<FormField
|
|
286
|
-
id="name"
|
|
287
|
-
label="Full Name"
|
|
288
|
-
required
|
|
289
|
-
error={errors.name}
|
|
290
|
-
>
|
|
369
|
+
<FormField id="name" label="Full Name" required error={errors.name}>
|
|
291
370
|
<div className="relative">
|
|
292
371
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
293
|
-
<svg
|
|
294
|
-
|
|
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
|
+
/>
|
|
295
383
|
</svg>
|
|
296
384
|
</div>
|
|
297
385
|
<Input
|
|
@@ -301,12 +389,13 @@ export default function DataSubjectRequestForm({
|
|
|
301
389
|
onChange={handleChange}
|
|
302
390
|
className={cn(
|
|
303
391
|
"pl-10",
|
|
304
|
-
errors.name &&
|
|
392
|
+
errors.name &&
|
|
393
|
+
"border-red-300 focus:border-red-500 focus:ring-red-500",
|
|
305
394
|
)}
|
|
306
395
|
/>
|
|
307
396
|
</div>
|
|
308
397
|
</FormField>
|
|
309
|
-
|
|
398
|
+
|
|
310
399
|
<FormField
|
|
311
400
|
id="email"
|
|
312
401
|
label="Email Address"
|
|
@@ -315,7 +404,12 @@ export default function DataSubjectRequestForm({
|
|
|
315
404
|
>
|
|
316
405
|
<div className="relative">
|
|
317
406
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
318
|
-
<svg
|
|
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
|
+
>
|
|
319
413
|
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
|
320
414
|
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
|
321
415
|
</svg>
|
|
@@ -328,14 +422,15 @@ export default function DataSubjectRequestForm({
|
|
|
328
422
|
onChange={handleChange}
|
|
329
423
|
className={cn(
|
|
330
424
|
"pl-10",
|
|
331
|
-
errors.email &&
|
|
425
|
+
errors.email &&
|
|
426
|
+
"border-red-300 focus:border-red-500 focus:ring-red-500",
|
|
332
427
|
)}
|
|
333
428
|
/>
|
|
334
429
|
</div>
|
|
335
430
|
</FormField>
|
|
336
431
|
</div>
|
|
337
432
|
</div>
|
|
338
|
-
|
|
433
|
+
|
|
339
434
|
{/* Request Details Section */}
|
|
340
435
|
<FormField
|
|
341
436
|
id="details"
|
|
@@ -352,7 +447,8 @@ export default function DataSubjectRequestForm({
|
|
|
352
447
|
value={formData.details}
|
|
353
448
|
onChange={handleChange}
|
|
354
449
|
className={cn(
|
|
355
|
-
errors.details &&
|
|
450
|
+
errors.details &&
|
|
451
|
+
"border-red-300 focus:border-red-500 focus:ring-red-500",
|
|
356
452
|
)}
|
|
357
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."
|
|
358
454
|
/>
|
|
@@ -361,42 +457,57 @@ export default function DataSubjectRequestForm({
|
|
|
361
457
|
</div>
|
|
362
458
|
</div>
|
|
363
459
|
</FormField>
|
|
364
|
-
|
|
460
|
+
|
|
365
461
|
{/* Consent Checkbox */}
|
|
366
462
|
<div className="mt-4">
|
|
367
463
|
<Checkbox
|
|
368
464
|
id="consent"
|
|
369
465
|
name="consent"
|
|
370
466
|
checked={formData.consent}
|
|
371
|
-
onChange={(e) =>
|
|
467
|
+
onChange={(e) =>
|
|
468
|
+
setFormData({ ...formData, consent: e.target.checked })
|
|
469
|
+
}
|
|
372
470
|
label="I consent to the processing of my personal data"
|
|
373
471
|
description="By submitting this form, you consent to our processing of your personal data for the purpose of handling your request."
|
|
374
472
|
/>
|
|
375
473
|
{errors.consent && (
|
|
376
|
-
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
|
|
474
|
+
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
|
|
475
|
+
{errors.consent}
|
|
476
|
+
</p>
|
|
377
477
|
)}
|
|
378
478
|
</div>
|
|
379
|
-
|
|
479
|
+
|
|
380
480
|
{errors.submit && (
|
|
381
481
|
<div className="rounded-md bg-red-50 dark:bg-red-900/20 p-4 flex items-start">
|
|
382
|
-
<svg
|
|
383
|
-
|
|
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
|
+
/>
|
|
384
493
|
</svg>
|
|
385
|
-
<p className="text-sm text-red-700 dark:text-red-400">
|
|
494
|
+
<p className="text-sm text-red-700 dark:text-red-400">
|
|
495
|
+
{errors.submit}
|
|
496
|
+
</p>
|
|
386
497
|
</div>
|
|
387
498
|
)}
|
|
388
|
-
|
|
499
|
+
|
|
389
500
|
<div className="flex justify-end pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
390
501
|
<Button
|
|
391
502
|
type="button"
|
|
392
503
|
variant="secondary"
|
|
393
504
|
onClick={() => {
|
|
394
505
|
setFormData({
|
|
395
|
-
requestType:
|
|
396
|
-
name:
|
|
397
|
-
email:
|
|
398
|
-
details:
|
|
399
|
-
consent: false
|
|
506
|
+
requestType: "access",
|
|
507
|
+
name: "",
|
|
508
|
+
email: "",
|
|
509
|
+
details: "",
|
|
510
|
+
consent: false,
|
|
400
511
|
});
|
|
401
512
|
setErrors({});
|
|
402
513
|
}}
|