@tantainnovative/ndpr-toolkit 1.0.4 → 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/eslint.config.mjs +16 -0
- package/next.config.js +15 -0
- package/next.config.ts +62 -0
- package/package.json +1 -1
- package/packages/ndpr-toolkit/README.md +467 -0
- 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/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/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/packages/ndpr-toolkit/src/index.ts +44 -0
- 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/src/app/accessibility.css +70 -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 +193 -0
- package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +530 -0
- package/src/components/dpia/DPIAQuestionnaire.tsx +523 -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 +361 -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 +226 -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 +70 -0
- package/src/hooks/useLoadingState.ts +85 -0
- package/src/lib/consentService.ts +144 -0
- package/src/lib/dpiaQuestions.ts +188 -0
- package/src/lib/requestService.ts +79 -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 +77 -0
- package/tailwind.config.ts +65 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { DPIAResult, DPIARisk } from '../types/dpia';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Assesses the risk level of a DPIA based on the identified risks
|
|
5
|
+
* @param dpiaResult The DPIA result containing risks to assess
|
|
6
|
+
* @returns Assessment result with overall risk level and recommendations
|
|
7
|
+
*/
|
|
8
|
+
export function assessDPIARisk(dpiaResult: DPIAResult): {
|
|
9
|
+
overallRiskLevel: 'low' | 'medium' | 'high' | 'critical';
|
|
10
|
+
requiresConsultation: boolean;
|
|
11
|
+
canProceed: boolean;
|
|
12
|
+
recommendations: string[];
|
|
13
|
+
} {
|
|
14
|
+
// Count risks by level
|
|
15
|
+
const riskCounts = {
|
|
16
|
+
low: 0,
|
|
17
|
+
medium: 0,
|
|
18
|
+
high: 0,
|
|
19
|
+
critical: 0
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Calculate the highest risk score
|
|
23
|
+
let highestRiskScore = 0;
|
|
24
|
+
|
|
25
|
+
// Track unmitigated high/critical risks
|
|
26
|
+
const unmitigatedHighRisks: DPIARisk[] = [];
|
|
27
|
+
|
|
28
|
+
// Analyze each risk
|
|
29
|
+
dpiaResult.risks.forEach(risk => {
|
|
30
|
+
// Count by level
|
|
31
|
+
riskCounts[risk.level]++;
|
|
32
|
+
|
|
33
|
+
// Track highest score
|
|
34
|
+
if (risk.score > highestRiskScore) {
|
|
35
|
+
highestRiskScore = risk.score;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Track unmitigated high/critical risks
|
|
39
|
+
if ((risk.level === 'high' || risk.level === 'critical') && !risk.mitigated) {
|
|
40
|
+
unmitigatedHighRisks.push(risk);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Determine overall risk level
|
|
45
|
+
let overallRiskLevel: 'low' | 'medium' | 'high' | 'critical';
|
|
46
|
+
|
|
47
|
+
if (riskCounts.critical > 0) {
|
|
48
|
+
overallRiskLevel = 'critical';
|
|
49
|
+
} else if (riskCounts.high > 2 || (riskCounts.high > 0 && riskCounts.medium > 3)) {
|
|
50
|
+
overallRiskLevel = 'high';
|
|
51
|
+
} else if (riskCounts.high > 0 || riskCounts.medium > 1) {
|
|
52
|
+
overallRiskLevel = 'medium';
|
|
53
|
+
} else {
|
|
54
|
+
overallRiskLevel = 'low';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Determine if NITDA consultation is required
|
|
58
|
+
// Under NDPR, consultation is required for high-risk processing
|
|
59
|
+
const requiresConsultation = overallRiskLevel === 'high' || overallRiskLevel === 'critical';
|
|
60
|
+
|
|
61
|
+
// Determine if processing can proceed
|
|
62
|
+
// Processing can proceed if all high/critical risks are mitigated
|
|
63
|
+
const canProceed = unmitigatedHighRisks.length === 0;
|
|
64
|
+
|
|
65
|
+
// Generate recommendations
|
|
66
|
+
const recommendations: string[] = [];
|
|
67
|
+
|
|
68
|
+
if (unmitigatedHighRisks.length > 0) {
|
|
69
|
+
recommendations.push(
|
|
70
|
+
`Mitigate the following high/critical risks before proceeding: ${
|
|
71
|
+
unmitigatedHighRisks.map(risk => risk.description).join(', ')
|
|
72
|
+
}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (requiresConsultation) {
|
|
77
|
+
recommendations.push(
|
|
78
|
+
'Consult with NITDA before proceeding with this processing activity, as required by NDPR for high-risk processing.'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (riskCounts.medium > 0) {
|
|
83
|
+
recommendations.push(
|
|
84
|
+
'Implement additional safeguards to reduce medium-level risks where possible.'
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (overallRiskLevel !== 'low') {
|
|
89
|
+
recommendations.push(
|
|
90
|
+
'Schedule a review of this DPIA in 6 months to reassess risks and effectiveness of mitigation measures.'
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
recommendations.push(
|
|
94
|
+
'Schedule a review of this DPIA in 12 months as part of regular compliance activities.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
overallRiskLevel,
|
|
100
|
+
requiresConsultation,
|
|
101
|
+
canProceed,
|
|
102
|
+
recommendations
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DSRRequest } from '../types/dsr';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Formats a DSR request for display or submission
|
|
5
|
+
* @param request The DSR request to format
|
|
6
|
+
* @returns Formatted request data
|
|
7
|
+
*/
|
|
8
|
+
export function formatDSRRequest(request: DSRRequest): {
|
|
9
|
+
formattedRequest: Record<string, any>;
|
|
10
|
+
isValid: boolean;
|
|
11
|
+
validationErrors: string[];
|
|
12
|
+
} {
|
|
13
|
+
const validationErrors: string[] = [];
|
|
14
|
+
|
|
15
|
+
// Validate required fields
|
|
16
|
+
if (!request.id) {
|
|
17
|
+
validationErrors.push('Request ID is required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!request.type) {
|
|
21
|
+
validationErrors.push('Request type is required');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!request.status) {
|
|
25
|
+
validationErrors.push('Request status is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!request.createdAt) {
|
|
29
|
+
validationErrors.push('Creation timestamp is required');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!request.subject?.name) {
|
|
33
|
+
validationErrors.push('Data subject name is required');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!request.subject?.email) {
|
|
37
|
+
validationErrors.push('Data subject email is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Format the request for display or submission
|
|
41
|
+
const formattedRequest = {
|
|
42
|
+
requestId: request.id,
|
|
43
|
+
requestType: request.type,
|
|
44
|
+
status: request.status,
|
|
45
|
+
createdDate: new Date(request.createdAt).toISOString(),
|
|
46
|
+
lastUpdated: request.updatedAt ? new Date(request.updatedAt).toISOString() : undefined,
|
|
47
|
+
dueDate: request.dueDate
|
|
48
|
+
? new Date(request.dueDate).toISOString()
|
|
49
|
+
: undefined,
|
|
50
|
+
dataSubject: {
|
|
51
|
+
name: request.subject.name,
|
|
52
|
+
email: request.subject.email,
|
|
53
|
+
phone: request.subject.phone || 'Not provided',
|
|
54
|
+
identifier: {
|
|
55
|
+
type: request.subject.identifierType || 'Not specified',
|
|
56
|
+
value: request.subject.identifierValue || 'Not provided'
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
additionalInformation: request.additionalInfo || {},
|
|
60
|
+
verificationStatus: request.verification
|
|
61
|
+
? `${request.verification.verified ? 'Verified' : 'Not verified'}${request.verification.method ? ` via ${request.verification.method}` : ''}`
|
|
62
|
+
: 'Pending verification',
|
|
63
|
+
attachments: request.attachments
|
|
64
|
+
? request.attachments.map(attachment => ({
|
|
65
|
+
name: attachment.name,
|
|
66
|
+
type: attachment.type,
|
|
67
|
+
addedOn: new Date(attachment.addedAt).toISOString()
|
|
68
|
+
}))
|
|
69
|
+
: []
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
formattedRequest,
|
|
74
|
+
isValid: validationErrors.length === 0,
|
|
75
|
+
validationErrors
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { PolicySection, OrganizationInfo, PolicyVariable } from '../types/privacy';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates policy text by replacing variables in a template with organization-specific values
|
|
5
|
+
* @param sectionsOrTemplate The policy sections or template string to generate text for
|
|
6
|
+
* @param organizationInfoOrVariables The organization information or variable map to use for replacement
|
|
7
|
+
* @returns The generated policy text or an object with the generated text and metadata
|
|
8
|
+
*/
|
|
9
|
+
export function generatePolicyText(
|
|
10
|
+
sectionsOrTemplate: PolicySection[] | string,
|
|
11
|
+
organizationInfoOrVariables: OrganizationInfo | Record<string, string>
|
|
12
|
+
): string | {
|
|
13
|
+
fullText: string;
|
|
14
|
+
sectionTexts: Record<string, string>;
|
|
15
|
+
missingVariables: string[];
|
|
16
|
+
} {
|
|
17
|
+
// Check if we're using the new API (template string and variable map)
|
|
18
|
+
if (typeof sectionsOrTemplate === 'string') {
|
|
19
|
+
const template = sectionsOrTemplate;
|
|
20
|
+
const variableMap = organizationInfoOrVariables as Record<string, string>;
|
|
21
|
+
|
|
22
|
+
// Replace variables in the template
|
|
23
|
+
let result = template;
|
|
24
|
+
const variableRegex = /\{\{([^}]+)\}\}/g;
|
|
25
|
+
let match;
|
|
26
|
+
|
|
27
|
+
// Find and replace all variables in the content
|
|
28
|
+
while ((match = variableRegex.exec(template)) !== null) {
|
|
29
|
+
const variable = match[1].trim();
|
|
30
|
+
const replacement = variableMap[variable] || '';
|
|
31
|
+
|
|
32
|
+
// Replace the variable in the content
|
|
33
|
+
result = result.replace(
|
|
34
|
+
new RegExp(`\\{\\{\\s*${variable}\\s*\\}\\}`, 'g'),
|
|
35
|
+
replacement
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
// Otherwise use the original API (sections array and organization info)
|
|
42
|
+
else {
|
|
43
|
+
const sections = sectionsOrTemplate;
|
|
44
|
+
const organizationInfo = organizationInfoOrVariables as OrganizationInfo;
|
|
45
|
+
const sectionTexts: Record<string, string> = {};
|
|
46
|
+
const missingVariables: string[] = [];
|
|
47
|
+
|
|
48
|
+
// Process each section
|
|
49
|
+
sections
|
|
50
|
+
.filter(section => section.included)
|
|
51
|
+
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
52
|
+
.forEach(section => {
|
|
53
|
+
// Use template if available, otherwise fall back to custom/default content
|
|
54
|
+
let content = section.template || section.customContent || section.defaultContent || '';
|
|
55
|
+
|
|
56
|
+
// Replace variables in the content
|
|
57
|
+
const variableRegex = /\{\{([^}]+)\}\}/g;
|
|
58
|
+
let match;
|
|
59
|
+
|
|
60
|
+
// Find all variables in the content
|
|
61
|
+
const contentVariables: string[] = [];
|
|
62
|
+
while ((match = variableRegex.exec(content)) !== null) {
|
|
63
|
+
contentVariables.push(match[1].trim());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Replace each variable with its value
|
|
67
|
+
contentVariables.forEach(variable => {
|
|
68
|
+
let replacement = '';
|
|
69
|
+
|
|
70
|
+
// Check if the variable exists in organizationInfo
|
|
71
|
+
if (variable in organizationInfo) {
|
|
72
|
+
replacement = organizationInfo[variable as keyof OrganizationInfo] as string || '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If replacement is empty, add to missing variables
|
|
76
|
+
if (!replacement) {
|
|
77
|
+
missingVariables.push(variable);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Replace the variable in the content
|
|
81
|
+
content = content.replace(
|
|
82
|
+
new RegExp(`\\{\\{\\s*${variable}\\s*\\}\\}`, 'g'),
|
|
83
|
+
replacement
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Store the processed section text
|
|
88
|
+
sectionTexts[section.id] = content;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Combine all sections into full text
|
|
92
|
+
const fullText = Object.values(sectionTexts).join('\n\n');
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
fullText,
|
|
96
|
+
sectionTexts,
|
|
97
|
+
missingVariables: Array.from(new Set(missingVariables)) // Remove duplicates
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es5",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"noFallthroughCasesInSwitch": true,
|
|
12
|
+
"module": "esnext",
|
|
13
|
+
"moduleResolution": "node",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"declaration": true,
|
|
18
|
+
"declarationDir": "dist",
|
|
19
|
+
"outDir": "dist"
|
|
20
|
+
},
|
|
21
|
+
"include": ["src"],
|
|
22
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* Accessibility-focused styles with improved color contrast */
|
|
2
|
+
|
|
3
|
+
/* Badge styles with WCAG AA compliant contrast ratios */
|
|
4
|
+
.badge-success {
|
|
5
|
+
@apply bg-green-700 text-white dark:bg-green-600 dark:text-white;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.badge-warning {
|
|
9
|
+
@apply bg-amber-700 text-white dark:bg-amber-600 dark:text-white;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.badge-error {
|
|
13
|
+
@apply bg-red-700 text-white dark:bg-red-600 dark:text-white;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.badge-info {
|
|
17
|
+
@apply bg-blue-700 text-white dark:bg-blue-600 dark:text-white;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.badge-neutral {
|
|
21
|
+
@apply bg-gray-700 text-white dark:bg-gray-500 dark:text-white;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Text colors with improved contrast */
|
|
25
|
+
.text-muted-accessible {
|
|
26
|
+
@apply text-gray-700 dark:text-gray-300;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.text-secondary-accessible {
|
|
30
|
+
@apply text-gray-600 dark:text-gray-400;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Focus indicators for better keyboard navigation */
|
|
34
|
+
.focus-visible-ring {
|
|
35
|
+
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-600 dark:focus-visible:ring-blue-400 focus-visible:ring-offset-background;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* High contrast mode support */
|
|
39
|
+
@media (prefers-contrast: high) {
|
|
40
|
+
.badge-success,
|
|
41
|
+
.badge-warning,
|
|
42
|
+
.badge-error,
|
|
43
|
+
.badge-info,
|
|
44
|
+
.badge-neutral {
|
|
45
|
+
@apply border-2 border-current;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.text-muted-accessible {
|
|
49
|
+
@apply text-gray-900 dark:text-gray-100;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Ensure interactive elements have sufficient size */
|
|
54
|
+
.interactive-target {
|
|
55
|
+
@apply min-h-[44px] min-w-[44px];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Skip link for keyboard navigation */
|
|
59
|
+
.skip-link {
|
|
60
|
+
@apply sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-blue-600 focus:text-white focus:rounded-md;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Aria live region styles */
|
|
64
|
+
.aria-live-polite {
|
|
65
|
+
@apply sr-only;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[role="alert"] {
|
|
69
|
+
@apply font-medium;
|
|
70
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "./accessibility.css";
|
|
4
|
+
|
|
5
|
+
@custom-variant dark (&:is(.dark *));
|
|
6
|
+
|
|
7
|
+
@theme inline {
|
|
8
|
+
--color-background: var(--background);
|
|
9
|
+
--color-foreground: var(--foreground);
|
|
10
|
+
--font-sans: var(--font-geist-sans);
|
|
11
|
+
--font-mono: var(--font-geist-mono);
|
|
12
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
13
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
14
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
15
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
16
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
17
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
18
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
19
|
+
--color-sidebar: var(--sidebar);
|
|
20
|
+
--color-chart-5: var(--chart-5);
|
|
21
|
+
--color-chart-4: var(--chart-4);
|
|
22
|
+
--color-chart-3: var(--chart-3);
|
|
23
|
+
--color-chart-2: var(--chart-2);
|
|
24
|
+
--color-chart-1: var(--chart-1);
|
|
25
|
+
--color-ring: var(--ring);
|
|
26
|
+
--color-input: var(--input);
|
|
27
|
+
--color-border: var(--border);
|
|
28
|
+
--color-destructive: var(--destructive);
|
|
29
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
30
|
+
--color-accent: var(--accent);
|
|
31
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
32
|
+
--color-muted: var(--muted);
|
|
33
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
34
|
+
--color-secondary: var(--secondary);
|
|
35
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
36
|
+
--color-primary: var(--primary);
|
|
37
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
38
|
+
--color-popover: var(--popover);
|
|
39
|
+
--color-card-foreground: var(--card-foreground);
|
|
40
|
+
--color-card: var(--card);
|
|
41
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
42
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
43
|
+
--radius-lg: var(--radius);
|
|
44
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:root {
|
|
48
|
+
--radius: 0.625rem;
|
|
49
|
+
--background: oklch(1 0 0);
|
|
50
|
+
--foreground: oklch(0.13 0.028 261.692);
|
|
51
|
+
--card: oklch(1 0 0);
|
|
52
|
+
--card-foreground: oklch(0.13 0.028 261.692);
|
|
53
|
+
--popover: oklch(1 0 0);
|
|
54
|
+
--popover-foreground: oklch(0.13 0.028 261.692);
|
|
55
|
+
--primary: oklch(0.21 0.034 264.665);
|
|
56
|
+
--primary-foreground: oklch(0.985 0.002 247.839);
|
|
57
|
+
--secondary: oklch(0.967 0.003 264.542);
|
|
58
|
+
--secondary-foreground: oklch(0.21 0.034 264.665);
|
|
59
|
+
--muted: oklch(0.967 0.003 264.542);
|
|
60
|
+
--muted-foreground: oklch(0.551 0.027 264.364);
|
|
61
|
+
--accent: oklch(0.967 0.003 264.542);
|
|
62
|
+
--accent-foreground: oklch(0.21 0.034 264.665);
|
|
63
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
64
|
+
--border: oklch(0.928 0.006 264.531);
|
|
65
|
+
--input: oklch(0.928 0.006 264.531);
|
|
66
|
+
--ring: oklch(0.707 0.022 261.325);
|
|
67
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
68
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
69
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
70
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
71
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
72
|
+
--sidebar: oklch(0.985 0.002 247.839);
|
|
73
|
+
--sidebar-foreground: oklch(0.13 0.028 261.692);
|
|
74
|
+
--sidebar-primary: oklch(0.21 0.034 264.665);
|
|
75
|
+
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
|
76
|
+
--sidebar-accent: oklch(0.967 0.003 264.542);
|
|
77
|
+
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
|
|
78
|
+
--sidebar-border: oklch(0.928 0.006 264.531);
|
|
79
|
+
--sidebar-ring: oklch(0.707 0.022 261.325);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.dark {
|
|
83
|
+
--background: oklch(0.13 0.028 261.692);
|
|
84
|
+
--foreground: oklch(0.985 0.002 247.839);
|
|
85
|
+
--card: oklch(0.21 0.034 264.665);
|
|
86
|
+
--card-foreground: oklch(0.985 0.002 247.839);
|
|
87
|
+
--popover: oklch(0.21 0.034 264.665);
|
|
88
|
+
--popover-foreground: oklch(0.985 0.002 247.839);
|
|
89
|
+
--primary: oklch(0.928 0.006 264.531);
|
|
90
|
+
--primary-foreground: oklch(0.21 0.034 264.665);
|
|
91
|
+
--secondary: oklch(0.278 0.033 256.848);
|
|
92
|
+
--secondary-foreground: oklch(0.985 0.002 247.839);
|
|
93
|
+
--muted: oklch(0.278 0.033 256.848);
|
|
94
|
+
--muted-foreground: oklch(0.707 0.022 261.325);
|
|
95
|
+
--accent: oklch(0.278 0.033 256.848);
|
|
96
|
+
--accent-foreground: oklch(0.985 0.002 247.839);
|
|
97
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
98
|
+
--border: oklch(1 0 0 / 10%);
|
|
99
|
+
--input: oklch(1 0 0 / 15%);
|
|
100
|
+
--ring: oklch(0.551 0.027 264.364);
|
|
101
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
102
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
103
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
104
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
105
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
106
|
+
--sidebar: oklch(0.21 0.034 264.665);
|
|
107
|
+
--sidebar-foreground: oklch(0.985 0.002 247.839);
|
|
108
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
109
|
+
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
|
110
|
+
--sidebar-accent: oklch(0.278 0.033 256.848);
|
|
111
|
+
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
|
|
112
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
113
|
+
--sidebar-ring: oklch(0.551 0.027 264.364);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@layer base {
|
|
117
|
+
* {
|
|
118
|
+
@apply border-border outline-ring/50;
|
|
119
|
+
}
|
|
120
|
+
body {
|
|
121
|
+
@apply bg-background text-foreground;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
|
4
|
+
import "./globals.css";
|
|
5
|
+
|
|
6
|
+
const geistSans = Geist({
|
|
7
|
+
variable: "--font-geist-sans",
|
|
8
|
+
subsets: ["latin"],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const geistMono = Geist_Mono({
|
|
12
|
+
variable: "--font-geist-mono",
|
|
13
|
+
subsets: ["latin"],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const metadata: Metadata = {
|
|
17
|
+
title: "NDPR Toolkit - Nigerian Data Protection Compliance",
|
|
18
|
+
description: "Open-source toolkit for implementing NDPR and DPA compliant features in Nigerian applications",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default function RootLayout({
|
|
22
|
+
children,
|
|
23
|
+
}: Readonly<{
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}>) {
|
|
26
|
+
return (
|
|
27
|
+
<html lang="en">
|
|
28
|
+
<body
|
|
29
|
+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
30
|
+
>
|
|
31
|
+
<ErrorBoundary>
|
|
32
|
+
{children}
|
|
33
|
+
</ErrorBoundary>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
36
|
+
);
|
|
37
|
+
}
|