@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.
Files changed (110) hide show
  1. package/next-env.d.ts +5 -0
  2. package/package.json +1 -1
  3. package/packages/ndpr-toolkit/dist/components/breach/BreachNotificationManager.d.ts +62 -0
  4. package/packages/ndpr-toolkit/dist/components/breach/BreachReportForm.d.ts +66 -0
  5. package/packages/ndpr-toolkit/dist/components/breach/BreachRiskAssessment.d.ts +50 -0
  6. package/packages/ndpr-toolkit/dist/components/breach/RegulatoryReportGenerator.d.ts +94 -0
  7. package/packages/ndpr-toolkit/dist/components/consent/ConsentBanner.d.ts +79 -0
  8. package/packages/ndpr-toolkit/dist/components/consent/ConsentManager.d.ts +73 -0
  9. package/packages/ndpr-toolkit/dist/components/consent/ConsentStorage.d.ts +41 -0
  10. package/packages/ndpr-toolkit/dist/components/dpia/DPIAQuestionnaire.d.ts +70 -0
  11. package/packages/ndpr-toolkit/dist/components/dpia/DPIAReport.d.ts +40 -0
  12. package/packages/ndpr-toolkit/dist/components/dpia/StepIndicator.d.ts +64 -0
  13. package/packages/ndpr-toolkit/dist/components/dsr/DSRDashboard.d.ts +58 -0
  14. package/packages/ndpr-toolkit/dist/components/dsr/DSRRequestForm.d.ts +74 -0
  15. package/packages/ndpr-toolkit/dist/components/dsr/DSRTracker.d.ts +56 -0
  16. package/packages/ndpr-toolkit/dist/components/policy/PolicyExporter.d.ts +65 -0
  17. package/packages/ndpr-toolkit/dist/components/policy/PolicyGenerator.d.ts +54 -0
  18. package/packages/ndpr-toolkit/dist/components/policy/PolicyPreview.d.ts +71 -0
  19. package/packages/ndpr-toolkit/dist/hooks/useBreach.d.ts +97 -0
  20. package/packages/ndpr-toolkit/dist/hooks/useConsent.d.ts +63 -0
  21. package/packages/ndpr-toolkit/dist/hooks/useDPIA.d.ts +92 -0
  22. package/packages/ndpr-toolkit/dist/hooks/useDSR.d.ts +72 -0
  23. package/packages/ndpr-toolkit/dist/hooks/usePrivacyPolicy.d.ts +87 -0
  24. package/packages/ndpr-toolkit/dist/index.d.ts +31 -0
  25. package/packages/ndpr-toolkit/dist/index.esm.js +2 -0
  26. package/packages/ndpr-toolkit/dist/index.esm.js.map +1 -0
  27. package/packages/ndpr-toolkit/dist/index.js +2 -0
  28. package/packages/ndpr-toolkit/dist/index.js.map +1 -0
  29. package/packages/ndpr-toolkit/dist/setupTests.d.ts +2 -0
  30. package/packages/ndpr-toolkit/dist/types/breach.d.ts +239 -0
  31. package/packages/ndpr-toolkit/dist/types/consent.d.ts +95 -0
  32. package/packages/ndpr-toolkit/dist/types/dpia.d.ts +196 -0
  33. package/packages/ndpr-toolkit/dist/types/dsr.d.ts +162 -0
  34. package/packages/ndpr-toolkit/dist/types/privacy.d.ts +204 -0
  35. package/packages/ndpr-toolkit/dist/utils/breach.d.ts +14 -0
  36. package/packages/ndpr-toolkit/dist/utils/consent.d.ts +10 -0
  37. package/packages/ndpr-toolkit/dist/utils/dpia.d.ts +12 -0
  38. package/packages/ndpr-toolkit/dist/utils/dsr.d.ts +11 -0
  39. package/packages/ndpr-toolkit/dist/utils/privacy.d.ts +12 -0
  40. package/src/components/consent/ConsentBanner.tsx +82 -48
  41. package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +240 -129
  42. package/src/components/dpia/DPIAQuestionnaire.tsx +162 -122
  43. package/src/components/privacy-policy/PolicyGenerator.tsx +5 -5
  44. package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +103 -77
  45. package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +117 -63
  46. package/src/hooks/useConsent.ts +16 -10
  47. package/src/lib/consentService.ts +44 -37
  48. package/src/lib/dpiaQuestions.ts +139 -99
  49. package/src/lib/requestService.ts +21 -17
  50. package/src/types/index.ts +13 -8
  51. package/.claude/settings.local.json +0 -20
  52. package/.eslintrc.json +0 -10
  53. package/.github/workflows/ci.yml +0 -36
  54. package/.github/workflows/nextjs.yml +0 -104
  55. package/.husky/commit-msg +0 -4
  56. package/.husky/pre-commit +0 -4
  57. package/.lintstagedrc.js +0 -4
  58. package/.nvmrc +0 -1
  59. package/.versionrc +0 -17
  60. package/CLAUDE.md +0 -90
  61. package/commitlint.config.js +0 -36
  62. package/jest.config.js +0 -31
  63. package/jest.setup.js +0 -15
  64. package/packages/ndpr-toolkit/jest.config.js +0 -23
  65. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +0 -119
  66. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +0 -122
  67. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +0 -270
  68. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +0 -199
  69. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +0 -224
  70. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +0 -104
  71. package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +0 -161
  72. package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +0 -330
  73. package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +0 -149
  74. package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +0 -88
  75. package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +0 -160
  76. package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +0 -110
  77. package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +0 -97
  78. package/src/__tests__/example.test.ts +0 -13
  79. package/src/__tests__/requestService.test.ts +0 -57
  80. package/src/app/docs/components/DocLayout.tsx +0 -267
  81. package/src/app/docs/components/breach-notification/page.tsx +0 -797
  82. package/src/app/docs/components/consent-management/page.tsx +0 -576
  83. package/src/app/docs/components/data-subject-rights/page.tsx +0 -511
  84. package/src/app/docs/components/dpia-questionnaire/layout.tsx +0 -15
  85. package/src/app/docs/components/dpia-questionnaire/metadata.ts +0 -31
  86. package/src/app/docs/components/dpia-questionnaire/page.tsx +0 -666
  87. package/src/app/docs/components/hooks/page.tsx +0 -305
  88. package/src/app/docs/components/page.tsx +0 -84
  89. package/src/app/docs/components/privacy-policy-generator/page.tsx +0 -634
  90. package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +0 -123
  91. package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +0 -328
  92. package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +0 -28
  93. package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +0 -91
  94. package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +0 -118
  95. package/src/app/docs/guides/breach-notification-process/page.tsx +0 -39
  96. package/src/app/docs/guides/conducting-dpia/page.tsx +0 -593
  97. package/src/app/docs/guides/data-subject-requests/page.tsx +0 -666
  98. package/src/app/docs/guides/managing-consent/page.tsx +0 -738
  99. package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +0 -296
  100. package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +0 -145
  101. package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +0 -33
  102. package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +0 -99
  103. package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +0 -159
  104. package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +0 -38
  105. package/src/app/docs/guides/page.tsx +0 -67
  106. package/src/app/docs/layout.tsx +0 -15
  107. package/src/app/docs/metadata.ts +0 -31
  108. package/src/app/docs/page.tsx +0 -572
  109. package/src/components/docs/DocLayout.tsx +0 -289
  110. package/src/components/docs/index.ts +0 -2
@@ -1,60 +1,119 @@
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';
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 'access':
16
+ case "access":
17
17
  return (
18
- <svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
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 fillRule="evenodd" 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" clipRule="evenodd" />
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 'rectification':
32
+ case "rectification":
24
33
  return (
25
- <svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
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 'erasure':
43
+ case "erasure":
30
44
  return (
31
- <svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
32
- <path fillRule="evenodd" 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" clipRule="evenodd" />
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 'restriction':
58
+ case "restriction":
36
59
  return (
37
- <svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
38
- <path fillRule="evenodd" 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" clipRule="evenodd" />
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 'portability':
73
+ case "portability":
42
74
  return (
43
- <svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
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 fillRule="evenodd" 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" clipRule="evenodd" />
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 'objection':
89
+ case "objection":
49
90
  return (
50
- <svg className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
51
- <path fillRule="evenodd" 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" clipRule="evenodd" />
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 className="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
57
- <path fillRule="evenodd" 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" clipRule="evenodd" />
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: 'access' as 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: { value: RequestType; label: string; description: string }[] = [
148
+ const requestTypeOptions: {
149
+ value: RequestType;
150
+ label: string;
151
+ description: string;
152
+ }[] = [
90
153
  {
91
- value: 'access',
92
- label: 'Access to Personal Data',
93
- description: 'Request a copy of your personal data that we process',
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: 'rectification',
97
- label: 'Rectification of Data',
98
- description: 'Request correction of inaccurate personal data',
159
+ value: "rectification",
160
+ label: "Rectification of Data",
161
+ description: "Request correction of inaccurate personal data",
99
162
  },
100
163
  {
101
- value: 'erasure',
102
- label: 'Erasure of Data',
103
- description: 'Request deletion of your personal data ("right to be forgotten")',
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: 'restriction',
107
- label: 'Restriction of Processing',
108
- description: 'Request to restrict how we process your data',
170
+ value: "restriction",
171
+ label: "Restriction of Processing",
172
+ description: "Request to restrict how we process your data",
109
173
  },
110
174
  {
111
- value: 'portability',
112
- label: 'Data Portability',
113
- description: 'Request to receive your data in a structured, machine-readable format',
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: 'objection',
117
- label: 'Object to Processing',
118
- description: 'Object to processing of your personal data',
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<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
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 = 'Name is required';
212
+ newErrors.name = "Name is required";
146
213
  }
147
-
214
+
148
215
  if (!formData.email.trim()) {
149
- newErrors.email = 'Email is required';
216
+ newErrors.email = "Email is required";
150
217
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
151
- newErrors.email = 'Please enter a valid email address';
218
+ newErrors.email = "Please enter a valid email address";
152
219
  }
153
-
220
+
154
221
  if (!formData.details.trim()) {
155
- newErrors.details = 'Please provide details about your request';
222
+ newErrors.details = "Please provide details about your request";
156
223
  }
157
-
224
+
158
225
  if (!formData.consent) {
159
- newErrors.consent = 'You must consent to the processing of your data to submit this request';
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: 'access',
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('Error submitting request:', error);
262
+ console.error("Error submitting request:", error);
195
263
  setErrors({
196
- submit: 'An error occurred while submitting your request. Please try again.',
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 className={`bg-white dark:bg-gray-800 shadow-lg rounded-lg overflow-hidden ${className}`}>
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 htmlFor="requestType" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
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={() => setFormData({...formData, requestType: option.value})}
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 className={cn(
262
- "w-10 h-10 flex items-center justify-center rounded-full mb-2",
263
- formData.requestType === option.value
264
- ? "bg-blue-100 text-blue-600 dark:bg-blue-800 dark:text-blue-300"
265
- : "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
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
- {requestTypeOptions.find((option) => option.value === formData.requestType)?.description}
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">Personal Information</h3>
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 className="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
294
- <path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
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 && "border-red-300 focus:border-red-500 focus:ring-red-500"
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 className="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
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 && "border-red-300 focus:border-red-500 focus:ring-red-500"
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 && "border-red-300 focus:border-red-500 focus:ring-red-500"
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) => setFormData({...formData, consent: e.target.checked})}
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">{errors.consent}</p>
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 className="h-5 w-5 text-red-400 mt-0.5 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
383
- <path fillRule="evenodd" 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" clipRule="evenodd" />
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">{errors.submit}</p>
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: 'access',
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
  }}