Compare commits

...
Author SHA1 Message Date
xirf ae3dd8c3f1 feat(crypto): add certificate hash generation and verification functions
- Implemented `generateCertificateHash` to create a hash using HMAC-SHA256 based on user ID and team ID.
- Added `verifyCertificateHash` to validate a given certificate ID against generated hash.
- Introduced `crypto-js` library as a dependency for cryptographic functions.

chore(deps): update ajv to version 8.13.0 and add crypto-js dependency

- Updated `ajv` from version 6.12.6 to 8.13.0 in package.json and package-lock.json.
- Added `crypto-js` library to package.json for cryptographic operations.
- Included type definitions for `crypto-js` in development dependencies.
2025-12-06 17:35:30 +07:00
9 changed files with 5052 additions and 131 deletions
+5 -2
View File
@@ -1,3 +1,6 @@
VITE_API_URL=https://forsomereason.dev/api/v1
VITE_GITHUB_CLIENT_ID=SuperSecretClientID12345
VITE_SUPABASE_URL=https://your-project-id.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key-here
VITE_SUPABASE_URL=https://mamahakumaurtx5090ti.supabase.co
VITE_SUPABASE_ANON_KEY=anonkeykekgimanasihbang
SUPABASE_SERVICE_ROLE_KEY=service.role.keyisahsecret
+13
View File
@@ -0,0 +1,13 @@
<!-- nx configuration start-->
<!-- Leave the start & end comments to automatically receive updates. -->
# General Guidelines for working with Nx
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
- You have access to the Nx MCP server and its tools, use them to help the user
- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable.
- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies
- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration
- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors
<!-- nx configuration end-->
@@ -0,0 +1,297 @@
'use client';
import { FC, ReactElement, useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router';
import {
useUserMe,
useMyTeams,
} from '@imphnen-frontend-service/service';
import { generateCertificateHash } from '@imphnen-frontend-service/utils';
import { Icon } from '@iconify/react';
interface Team {
id: string;
name: string;
city: string;
created_at: string;
}
const CertificatePage: FC = (): ReactElement => {
const { certId } = useParams<{ certId: string }>();
const navigate = useNavigate();
const { data: userData } = useUserMe();
const { data: teamsData } = useMyTeams();
const [isValid, setIsValid] = useState<boolean>(false);
const [validTeam, setValidTeam] = useState<Team | null>(null);
const [loading, setLoading] = useState(true);
const user = userData?.data;
// Add print styles
useEffect(() => {
const style = document.createElement('style');
style.textContent = `
@media print {
@page {
size: landscape;
margin: 0;
}
body {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
}
`;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, []);
// Verify certificate
useEffect(() => {
const teams = teamsData?.data || [];
if (!user || !teams.length || !certId) {
setLoading(true);
return;
}
const matchingTeam = teams.find((team: Team) => {
const generatedHash = generateCertificateHash(user.id, team.id);
return generatedHash === certId;
});
if (matchingTeam) {
setIsValid(true);
setValidTeam(matchingTeam as Team);
} else {
setIsValid(false);
setValidTeam(null);
}
setLoading(false);
}, [user, teamsData, certId]);
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
return date.toLocaleDateString('en-GB', {
day: '2-digit',
month: 'short',
year: 'numeric'
});
};
const handlePrint = () => {
window.print();
};
const handleDownload = () => {
window.print(); // Yoloooo
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-linear-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
<div className="text-center">
<Icon icon="eos-icons:loading" className="text-6xl text-primary-600 mb-4 mx-auto" />
<p className="text-gray-600 dark:text-gray-400">Validating certificate...</p>
</div>
</div>
);
}
if (!isValid || !validTeam || !user) {
return (
<div className="min-h-screen flex items-center justify-center bg-linear-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4">
<div className="text-center max-w-md">
<Icon icon="heroicons:x-circle-solid" className="text-8xl text-red-500 mb-6 mx-auto" />
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
Invalid Certificate
</h1>
<p className="text-gray-600 dark:text-gray-400 mb-8">
The certificate ID you provided is not valid or you don't have access to view this certificate.
</p>
<button
onClick={() => navigate('/dashboard')}
className="px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white rounded-lg transition-colors"
>
Go to Dashboard
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-linear-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4 md:p-8">
{/* Action buttons - hidden on print */}
<div className="max-w-7xl mx-auto mb-6 print:hidden">
<div className="flex justify-between items-center">
<button
onClick={() => navigate('/dashboard')}
className="flex items-center gap-2 px-4 py-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<Icon icon="heroicons:arrow-left" />
<span>Back to Dashboard</span>
</button>
<div className="flex gap-3">
<button
onClick={handleDownload}
className="flex items-center gap-2 px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
>
<Icon icon="heroicons:arrow-down-tray" />
<span>Download</span>
</button>
<button
onClick={handlePrint}
className="flex items-center gap-2 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
>
<Icon icon="heroicons:printer" />
<span>Print</span>
</button>
</div>
</div>
</div>
{/* Certificate */}
<div className="max-w-5xl mx-auto">
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl overflow-hidden print:shadow-none print:rounded-none">
{/* Decorative header pattern */}
<div className="h-3 bg-linear-to-r from-primary-400 via-primary-600 to-primary-400" />
<div className="p-12 md:p-16 relative">
{/* Background pattern */}
<div className="absolute inset-0 opacity-5 dark:opacity-10">
<div className="absolute inset-0" style={{
backgroundImage: `repeating-linear-gradient(
45deg,
transparent,
transparent 35px,
currentColor 35px,
currentColor 70px
)`
}} />
</div>
{/* Content */}
<div className="relative z-10">
{/* Header */}
<div className="flex justify-between items-start mb-12">
<div>
<h1 className="text-2xl md:text-3xl font-bold text-primary-600 dark:text-primary-400 mb-2">
IMPHNEN x KOLOSAL
</h1>
<p className="text-lg text-gray-600 dark:text-gray-400">
Hackathon 2025
</p>
</div>
<div className="text-right">
<div className="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-1">
PARTICIPANT
</div>
<div className="text-lg font-bold text-gray-700 dark:text-gray-300">
Certificate of Completion
</div>
</div>
</div>
{/* Badge/Icon */}
<div className="flex justify-center mb-8">
<div className="relative">
<div className="w-32 h-32 rounded-full bg-linear-to-br from-primary-400 to-primary-600 flex items-center justify-center shadow-lg">
<div className="bg-white dark:bg-slate-800 w-28 h-28 rounded-full flex items-center justify-center">
<Icon icon="heroicons:academic-cap-solid" className="text-6xl text-primary-600 dark:text-primary-400" />
</div>
</div>
{/* Laurel wreath effect */}
<div className="absolute -top-2 -left-2 text-yellow-500">
<Icon icon="game-icons:laurel-crown" className="text-4xl" />
</div>
<div className="absolute -bottom-2 -right-2 text-yellow-500">
<Icon icon="game-icons:laurel-crown" className="text-4xl transform rotate-180" />
</div>
</div>
</div>
{/* Certificate text */}
<div className="text-center mb-12">
<p className="text-lg text-gray-600 dark:text-gray-400 mb-6">
This award is presented to
</p>
<h2 className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-8 tracking-wide">
{user.fullname}
</h2>
<div className="max-w-2xl mx-auto">
<p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed">
for active participation and successful completion in the IMPHNEN x KOLOSAL Hackathon 2025
as a member of team <span className="font-semibold text-primary-600 dark:text-primary-400">"{validTeam.name}"</span> from{' '}
<span className="font-semibold">{validTeam.city}</span>.
</p>
</div>
</div>
{/* Signature and date */}
<div className="border-t border-gray-200 dark:border-gray-700 pt-8 mt-12">
<div className="flex justify-between items-end">
<div>
<div className="mb-8">
<div className="font-signature text-3xl text-gray-700 dark:text-gray-300 mb-2" style={{ fontFamily: 'cursive' }}>
IMPHNEN Team
</div>
</div>
<div className="text-sm font-semibold text-gray-700 dark:text-gray-300">
IMPHNEN Team
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Hackathon Organizer
</div>
</div>
<div className="text-right">
<div className="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-1">
{formatDate(validTeam.created_at)}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Completion Date
</div>
</div>
</div>
</div>
{/* Certificate ID footer */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
<p className="text-xs text-center text-gray-500 dark:text-gray-400 font-mono break-all">
Certificate ID: {certId}
</p>
</div>
</div>
</div>
{/* Decorative footer pattern */}
<div className="h-3 bg-linear-to-r from-primary-400 via-primary-600 to-primary-400" />
</div>
</div>
{/* Certificate verification info - hidden on print */}
<div className="max-w-5xl mx-auto mt-8 print:hidden">
<div className="bg-white dark:bg-slate-800 rounded-lg p-6 shadow-md">
<div className="flex items-start gap-3">
<Icon icon="heroicons:information-circle" className="text-2xl text-primary-600 mt-1 shrink-0" />
<div className="text-sm text-gray-600 dark:text-gray-400">
<p className="font-semibold mb-2">Certificate Verification</p>
<p>
This certificate is cryptographically verified using HMAC-SHA256.
The certificate ID is generated from your user ID and team ID, ensuring authenticity and uniqueness.
</p>
</div>
</div>
</div>
</div>
</div>
);
};
export default CertificatePage;
+4512
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -7,3 +7,5 @@ export * from './cookies';
export * from './session';
export * from './constants';
export * from './logic';
export * from './lib/crypto';
export * from './lib/certificate';
+18
View File
@@ -0,0 +1,18 @@
import { generateCertificateHash } from './crypto';
/**
* Generate a certificate URL for a user and team
* @param userId - User ID
* @param teamId - Team ID
* @param baseUrl - Base URL (optional, defaults to current origin)
* @returns Certificate URL
*/
export const generateCertificateUrl = (
userId: string,
teamId: string,
baseUrl?: string
): string => {
const certId = generateCertificateHash(userId, teamId);
const base = baseUrl || (typeof window !== 'undefined' ? window.location.origin : '');
return `${base}/certificate/${certId}`;
};
+31
View File
@@ -0,0 +1,31 @@
import CryptoJS from 'crypto-js';
/**
* Generate certificate hash using HMAC-SHA256
* @param userId - User ID
* @param teamId - Team ID
* @returns Hash string
*/
export const generateCertificateHash = (userId: string, teamId: string): string => {
const key = 'imphnenxkolosal';
const message = userId + teamId;
// Repeat key to match required length (32 bytes for SHA256)
const repeatedKey = key.repeat(Math.ceil(32 / key.length)).substring(0, 32);
// Create HMAC-SHA256 hash
const hash = CryptoJS.HmacSHA256(message, repeatedKey);
return hash.toString(CryptoJS.enc.Hex);
};
/**
* Verify if a certificate hash is valid for a given user and team
* @param certId - Certificate ID to verify
* @param userId - User ID
* @param teamId - Team ID
* @returns True if the certificate is valid
*/
export const verifyCertificateHash = (certId: string, userId: string, teamId: string): boolean => {
const generatedHash = generateCertificateHash(userId, teamId);
return generatedHash === certId;
};
+171 -128
View File
@@ -78,7 +78,7 @@
"@vitejs/plugin-react": "^4.2.0",
"@vitest/coverage-v8": "^3.0.5",
"@vitest/ui": "^3.0.0",
"ajv": "^6.12.6",
"ajv": "^8.13.0",
"eslint": "^9.8.0",
"eslint-config-next": "^15.2.4",
"eslint-config-prettier": "10.0.0",
@@ -216,6 +216,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -2688,6 +2689,28 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"license": "MIT"
},
"node_modules/@eslint/js": {
"version": "9.39.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
@@ -4119,6 +4142,7 @@
"integrity": "sha512-fnP+ZOZTFeBGiTAnxve+axGmiYn2D60h86nUISXjXClK3LUY1krUfPgf6MaD4YDJ4i51OGXZWPekeMe16pkd8Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@module-federation/runtime": "0.21.6",
"@module-federation/webpack-bundler-runtime": "0.21.6"
@@ -5251,23 +5275,6 @@
"vitest": "^1.3.1 || ^2.0.0 || ^3.0.0 || ^4.0.0"
}
},
"node_modules/@nx/vite/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@nx/vite/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
@@ -5380,23 +5387,6 @@
"webpack-subresource-integrity": "^5.1.0"
}
},
"node_modules/@nx/webpack/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@nx/workspace": {
"version": "22.1.1",
"resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-22.1.1.tgz",
@@ -5789,6 +5779,7 @@
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"playwright": "1.56.1"
},
@@ -7130,6 +7121,7 @@
"integrity": "sha512-5F1+MQD8rfbFbUHnaiZe4jqOu9pnSb+PliqQvi0lj+uvpMpcS3sJDIs/mz6P1u87lfkfBXChIT4zSLAzeOgMWw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@module-federation/runtime-tools": "0.21.4",
"@rspack/binding": "1.6.4",
@@ -7258,6 +7250,7 @@
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
@@ -7739,6 +7732,7 @@
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
@@ -8005,6 +7999,7 @@
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.8"
@@ -8220,6 +8215,7 @@
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
@@ -8230,6 +8226,7 @@
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/counter": "^0.1.3"
}
@@ -8543,6 +8540,7 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.10.tgz",
"integrity": "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tanstack/query-core": "5.90.10"
},
@@ -8621,6 +8619,7 @@
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -8809,6 +8808,7 @@
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
@@ -9136,6 +9136,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz",
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -9183,6 +9184,7 @@
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -9193,6 +9195,7 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -9359,6 +9362,7 @@
"integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.48.0",
"@typescript-eslint/types": "8.48.0",
@@ -9943,23 +9947,6 @@
"url": "https://opencollective.com/verdaccio"
}
},
"node_modules/@verdaccio/core/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@verdaccio/core/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@@ -10102,23 +10089,6 @@
"url": "https://opencollective.com/verdaccio"
}
},
"node_modules/@verdaccio/local-storage-legacy/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@verdaccio/local-storage-legacy/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@@ -10589,6 +10559,7 @@
"integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/utils": "3.2.4",
"fflate": "^0.8.2",
@@ -11133,6 +11104,7 @@
"integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -11180,6 +11152,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -11256,15 +11229,17 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
@@ -11289,39 +11264,6 @@
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/ajv/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"license": "MIT"
},
"node_modules/alien-signals": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz",
@@ -12446,6 +12388,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -12777,6 +12720,7 @@
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"readdirp": "^4.0.1"
},
@@ -14962,6 +14906,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -15112,6 +15057,7 @@
"deprecated": "wrong version missing main field",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "build/bin/cli.js"
},
@@ -15210,6 +15156,7 @@
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8",
@@ -15473,6 +15420,22 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint/node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
@@ -15485,6 +15448,12 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"license": "MIT"
},
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@@ -15971,6 +15940,41 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/file-loader/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/file-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/file-loader/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/file-loader/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@@ -16335,6 +16339,34 @@
}
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -16405,6 +16437,13 @@
"node": ">= 6"
}
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/fork-ts-checker-webpack-plugin/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -19508,6 +19547,7 @@
"integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
@@ -20989,6 +21029,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@napi-rs/wasm-runtime": "0.2.4",
"@yarnpkg/lockfile": "^1.1.0",
@@ -21355,6 +21396,7 @@
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.15.0.tgz",
"integrity": "sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"openapi-typescript-helpers": "^0.0.15"
}
@@ -22227,6 +22269,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -23294,6 +23337,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -23303,6 +23347,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -23315,6 +23360,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.1.tgz",
"integrity": "sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -23347,6 +23393,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -23595,7 +23642,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -23896,6 +23944,7 @@
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -25180,6 +25229,7 @@
"integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@@ -25201,6 +25251,7 @@
"integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@bufbuild/protobuf": "^2.5.0",
"buffer-builder": "^0.2.0",
@@ -25692,23 +25743,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/schema-utils/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/schema-utils/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
@@ -27814,6 +27848,7 @@
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -27894,7 +27929,8 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
"license": "0BSD",
"peer": true
},
"node_modules/tsscmp": {
"version": "1.0.6",
@@ -28076,6 +28112,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -28144,6 +28181,7 @@
"integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.40.0",
"@typescript-eslint/types": "8.40.0",
@@ -28829,6 +28867,7 @@
"integrity": "sha512-hMIcPES81Cx+9xMJtBrPvLDODV433CRcen+akIu4NRQEb6rBHuZfWMhPZi1J7Ri3wSKLgp3ahexVKrkb8tTbjQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@cypress/request": "3.0.9",
"@verdaccio/auth": "8.0.0-next-8.27",
@@ -29012,6 +29051,7 @@
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -29150,6 +29190,7 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -29300,6 +29341,7 @@
"integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -30011,6 +30053,7 @@
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
+3 -1
View File
@@ -46,6 +46,7 @@
"axios": "^1.8.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"framer-motion": "^12.9.2",
"graphql": "^16.11.0",
@@ -94,6 +95,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.6.1",
"@types/crypto-js": "^4.2.2",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.12.0",
"@types/react": "^19.1.2",
@@ -101,7 +103,7 @@
"@vitejs/plugin-react": "^4.2.0",
"@vitest/coverage-v8": "^3.0.5",
"@vitest/ui": "^3.0.0",
"ajv": "^6.12.6",
"ajv": "^8.13.0",
"eslint": "^9.8.0",
"eslint-config-next": "^15.2.4",
"eslint-config-prettier": "10.0.0",