@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.
Files changed (132) hide show
  1. package/eslint.config.mjs +16 -0
  2. package/next.config.js +15 -0
  3. package/next.config.ts +62 -0
  4. package/package.json +1 -1
  5. package/packages/ndpr-toolkit/README.md +467 -0
  6. package/packages/ndpr-toolkit/dist/components/breach/BreachNotificationManager.d.ts +62 -0
  7. package/packages/ndpr-toolkit/dist/components/breach/BreachReportForm.d.ts +66 -0
  8. package/packages/ndpr-toolkit/dist/components/breach/BreachRiskAssessment.d.ts +50 -0
  9. package/packages/ndpr-toolkit/dist/components/breach/RegulatoryReportGenerator.d.ts +94 -0
  10. package/packages/ndpr-toolkit/dist/components/consent/ConsentBanner.d.ts +79 -0
  11. package/packages/ndpr-toolkit/dist/components/consent/ConsentManager.d.ts +73 -0
  12. package/packages/ndpr-toolkit/dist/components/consent/ConsentStorage.d.ts +41 -0
  13. package/packages/ndpr-toolkit/dist/components/dpia/DPIAQuestionnaire.d.ts +70 -0
  14. package/packages/ndpr-toolkit/dist/components/dpia/DPIAReport.d.ts +40 -0
  15. package/packages/ndpr-toolkit/dist/components/dpia/StepIndicator.d.ts +64 -0
  16. package/packages/ndpr-toolkit/dist/components/dsr/DSRDashboard.d.ts +58 -0
  17. package/packages/ndpr-toolkit/dist/components/dsr/DSRRequestForm.d.ts +74 -0
  18. package/packages/ndpr-toolkit/dist/components/dsr/DSRTracker.d.ts +56 -0
  19. package/packages/ndpr-toolkit/dist/components/policy/PolicyExporter.d.ts +65 -0
  20. package/packages/ndpr-toolkit/dist/components/policy/PolicyGenerator.d.ts +54 -0
  21. package/packages/ndpr-toolkit/dist/components/policy/PolicyPreview.d.ts +71 -0
  22. package/packages/ndpr-toolkit/dist/hooks/useBreach.d.ts +97 -0
  23. package/packages/ndpr-toolkit/dist/hooks/useConsent.d.ts +63 -0
  24. package/packages/ndpr-toolkit/dist/hooks/useDPIA.d.ts +92 -0
  25. package/packages/ndpr-toolkit/dist/hooks/useDSR.d.ts +72 -0
  26. package/packages/ndpr-toolkit/dist/hooks/usePrivacyPolicy.d.ts +87 -0
  27. package/packages/ndpr-toolkit/dist/index.d.ts +31 -0
  28. package/packages/ndpr-toolkit/dist/index.esm.js +2 -0
  29. package/packages/ndpr-toolkit/dist/index.esm.js.map +1 -0
  30. package/packages/ndpr-toolkit/dist/index.js +2 -0
  31. package/packages/ndpr-toolkit/dist/index.js.map +1 -0
  32. package/packages/ndpr-toolkit/dist/setupTests.d.ts +2 -0
  33. package/packages/ndpr-toolkit/dist/types/breach.d.ts +239 -0
  34. package/packages/ndpr-toolkit/dist/types/consent.d.ts +95 -0
  35. package/packages/ndpr-toolkit/dist/types/dpia.d.ts +196 -0
  36. package/packages/ndpr-toolkit/dist/types/dsr.d.ts +162 -0
  37. package/packages/ndpr-toolkit/dist/types/privacy.d.ts +204 -0
  38. package/packages/ndpr-toolkit/dist/utils/breach.d.ts +14 -0
  39. package/packages/ndpr-toolkit/dist/utils/consent.d.ts +10 -0
  40. package/packages/ndpr-toolkit/dist/utils/dpia.d.ts +12 -0
  41. package/packages/ndpr-toolkit/dist/utils/dsr.d.ts +11 -0
  42. package/packages/ndpr-toolkit/dist/utils/privacy.d.ts +12 -0
  43. package/packages/ndpr-toolkit/package-lock.json +8197 -0
  44. package/packages/ndpr-toolkit/package.json +71 -0
  45. package/packages/ndpr-toolkit/rollup.config.js +34 -0
  46. package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +701 -0
  47. package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +631 -0
  48. package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +569 -0
  49. package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +496 -0
  50. package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +270 -0
  51. package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +217 -0
  52. package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +206 -0
  53. package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +342 -0
  54. package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +373 -0
  55. package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +174 -0
  56. package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +717 -0
  57. package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +476 -0
  58. package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +620 -0
  59. package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +541 -0
  60. package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +454 -0
  61. package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +333 -0
  62. package/packages/ndpr-toolkit/src/hooks/useBreach.ts +409 -0
  63. package/packages/ndpr-toolkit/src/hooks/useConsent.ts +263 -0
  64. package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +457 -0
  65. package/packages/ndpr-toolkit/src/hooks/useDSR.ts +236 -0
  66. package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +428 -0
  67. package/packages/ndpr-toolkit/src/index.ts +44 -0
  68. package/packages/ndpr-toolkit/src/setupTests.ts +5 -0
  69. package/packages/ndpr-toolkit/src/types/breach.ts +283 -0
  70. package/packages/ndpr-toolkit/src/types/consent.ts +111 -0
  71. package/packages/ndpr-toolkit/src/types/dpia.ts +236 -0
  72. package/packages/ndpr-toolkit/src/types/dsr.ts +192 -0
  73. package/packages/ndpr-toolkit/src/types/index.ts +42 -0
  74. package/packages/ndpr-toolkit/src/types/privacy.ts +246 -0
  75. package/packages/ndpr-toolkit/src/utils/breach.ts +122 -0
  76. package/packages/ndpr-toolkit/src/utils/consent.ts +51 -0
  77. package/packages/ndpr-toolkit/src/utils/dpia.ts +104 -0
  78. package/packages/ndpr-toolkit/src/utils/dsr.ts +77 -0
  79. package/packages/ndpr-toolkit/src/utils/privacy.ts +100 -0
  80. package/packages/ndpr-toolkit/tsconfig.json +23 -0
  81. package/postcss.config.mjs +5 -0
  82. package/src/app/accessibility.css +70 -0
  83. package/src/app/favicon.ico +0 -0
  84. package/src/app/globals.css +123 -0
  85. package/src/app/layout.tsx +37 -0
  86. package/src/app/ndpr-demos/breach/page.tsx +354 -0
  87. package/src/app/ndpr-demos/consent/page.tsx +366 -0
  88. package/src/app/ndpr-demos/dpia/page.tsx +495 -0
  89. package/src/app/ndpr-demos/dsr/page.tsx +280 -0
  90. package/src/app/ndpr-demos/page.tsx +73 -0
  91. package/src/app/ndpr-demos/policy/page.tsx +771 -0
  92. package/src/app/page.tsx +452 -0
  93. package/src/components/ErrorBoundary.tsx +90 -0
  94. package/src/components/breach-notification/BreachNotificationForm.tsx +479 -0
  95. package/src/components/consent/ConsentBanner.tsx +193 -0
  96. package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +530 -0
  97. package/src/components/dpia/DPIAQuestionnaire.tsx +523 -0
  98. package/src/components/privacy-policy/PolicyGenerator.tsx +1062 -0
  99. package/src/components/privacy-policy/data.ts +98 -0
  100. package/src/components/privacy-policy/shared/CheckboxField.tsx +38 -0
  101. package/src/components/privacy-policy/shared/CheckboxGroup.tsx +85 -0
  102. package/src/components/privacy-policy/shared/FormField.tsx +79 -0
  103. package/src/components/privacy-policy/shared/StepIndicator.tsx +86 -0
  104. package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +361 -0
  105. package/src/components/privacy-policy/steps/DataCollectionStep.tsx +231 -0
  106. package/src/components/privacy-policy/steps/DataSharingStep.tsx +418 -0
  107. package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +202 -0
  108. package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +226 -0
  109. package/src/components/ui/Badge.tsx +46 -0
  110. package/src/components/ui/Button.tsx +59 -0
  111. package/src/components/ui/Card.tsx +92 -0
  112. package/src/components/ui/Checkbox.tsx +57 -0
  113. package/src/components/ui/FormField.tsx +50 -0
  114. package/src/components/ui/Input.tsx +38 -0
  115. package/src/components/ui/Loading.tsx +201 -0
  116. package/src/components/ui/Select.tsx +42 -0
  117. package/src/components/ui/TextArea.tsx +38 -0
  118. package/src/components/ui/label.tsx +24 -0
  119. package/src/components/ui/switch.tsx +31 -0
  120. package/src/components/ui/tabs.tsx +66 -0
  121. package/src/hooks/useConsent.ts +70 -0
  122. package/src/hooks/useLoadingState.ts +85 -0
  123. package/src/lib/consentService.ts +144 -0
  124. package/src/lib/dpiaQuestions.ts +188 -0
  125. package/src/lib/requestService.ts +79 -0
  126. package/src/lib/sanitize.ts +108 -0
  127. package/src/lib/storage.ts +222 -0
  128. package/src/lib/utils.ts +6 -0
  129. package/src/types/html-to-docx.d.ts +30 -0
  130. package/src/types/index.ts +77 -0
  131. package/tailwind.config.ts +65 -0
  132. 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,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss", "autoprefixer"],
3
+ };
4
+
5
+ export default config;
@@ -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
+ }