From 01f87cc461d615382262cffcd8b0dcbdd9358e4e Mon Sep 17 00:00:00 2001 From: rahimi rahimi Date: Wed, 3 Jun 2026 18:31:41 +0330 Subject: [PATCH] first commit --- .gitignore | 76 + GhadirContest.sln | 22 + backend/ClientApp/.gitignore | 24 + backend/ClientApp/Dockerfile | 16 + backend/ClientApp/README.md | 73 + backend/ClientApp/eslint.config.js | 22 + backend/ClientApp/index.html | 16 + backend/ClientApp/nginx.conf | 38 + backend/ClientApp/package-lock.json | 4763 +++++++++++++++++ backend/ClientApp/package.json | 46 + backend/ClientApp/public/favicon.svg | 1 + backend/ClientApp/public/icons.svg | 24 + backend/ClientApp/src/App.tsx | 47 + backend/ClientApp/src/assets/hero.png | Bin 0 -> 13057 bytes backend/ClientApp/src/assets/react.svg | 1 + backend/ClientApp/src/assets/vite.svg | 1 + .../ClientApp/src/components/ui/button.tsx | 51 + backend/ClientApp/src/components/ui/card.tsx | 69 + .../ClientApp/src/components/ui/checkbox.tsx | 27 + .../ClientApp/src/components/ui/dialog.tsx | 116 + backend/ClientApp/src/components/ui/input.tsx | 21 + backend/ClientApp/src/components/ui/label.tsx | 20 + .../src/components/ui/radio-group.tsx | 41 + backend/ClientApp/src/components/ui/table.tsx | 105 + backend/ClientApp/src/components/ui/tabs.tsx | 52 + backend/ClientApp/src/context/UserContext.tsx | 53 + backend/ClientApp/src/index.css | 78 + backend/ClientApp/src/lib/api.ts | 123 + backend/ClientApp/src/lib/utils.ts | 6 + backend/ClientApp/src/main.tsx | 10 + backend/ClientApp/src/pages/AdminPage.tsx | 211 + backend/ClientApp/src/pages/DashboardPage.tsx | 481 ++ backend/ClientApp/src/pages/LandingPage.tsx | 298 ++ backend/ClientApp/tsconfig.app.json | 29 + backend/ClientApp/tsconfig.json | 7 + backend/ClientApp/tsconfig.node.json | 24 + backend/ClientApp/vite.config.ts | 27 + backend/Controllers/AdminController.cs | 58 + backend/Controllers/ConfigController.cs | 31 + backend/Controllers/KhatmController.cs | 105 + backend/Controllers/QuizController.cs | 165 + backend/Controllers/UserController.cs | 129 + backend/DTOs/AdminDtos.cs | 15 + backend/DTOs/KhatmDtos.cs | 27 + backend/DTOs/QuizDtos.cs | 45 + backend/DTOs/UserDtos.cs | 32 + backend/Data/AppDbContext.cs | 192 + backend/Dockerfile | 25 + .../20260603125735_InitialCreate.Designer.cs | 374 ++ .../20260603125735_InitialCreate.cs | 253 + ...20260603142557_AddKhatmItemUrl.Designer.cs | 381 ++ .../20260603142557_AddKhatmItemUrl.cs | 50 + ...44334_UpdateTrackingCodeLength.Designer.cs | 381 ++ ...20260603144334_UpdateTrackingCodeLength.cs | 57 + ...260603144804_SeedQuizQuestions.Designer.cs | 451 ++ .../20260603144804_SeedQuizQuestions.cs | 111 + .../Migrations/AppDbContextModelSnapshot.cs | 448 ++ backend/Models/AppConfig.cs | 23 + backend/Models/KhatmItem.cs | 24 + backend/Models/QuizQuestion.cs | 37 + backend/Models/User.cs | 28 + backend/Models/UserKhatmSelection.cs | 28 + backend/Models/UserQuizAnswer.cs | 29 + backend/Models/UserQuizAttempt.cs | 28 + backend/Models/UserTotalScore.cs | 20 + backend/Program.cs | 64 + backend/Properties/launchSettings.json | 28 + backend/appsettings.json | 32 + backend/backend.csproj | 59 + backend/backend.http | 6 + docker-compose.yml | 47 + 71 files changed, 10802 insertions(+) create mode 100644 .gitignore create mode 100644 GhadirContest.sln create mode 100644 backend/ClientApp/.gitignore create mode 100644 backend/ClientApp/Dockerfile create mode 100644 backend/ClientApp/README.md create mode 100644 backend/ClientApp/eslint.config.js create mode 100644 backend/ClientApp/index.html create mode 100644 backend/ClientApp/nginx.conf create mode 100644 backend/ClientApp/package-lock.json create mode 100644 backend/ClientApp/package.json create mode 100644 backend/ClientApp/public/favicon.svg create mode 100644 backend/ClientApp/public/icons.svg create mode 100644 backend/ClientApp/src/App.tsx create mode 100644 backend/ClientApp/src/assets/hero.png create mode 100644 backend/ClientApp/src/assets/react.svg create mode 100644 backend/ClientApp/src/assets/vite.svg create mode 100644 backend/ClientApp/src/components/ui/button.tsx create mode 100644 backend/ClientApp/src/components/ui/card.tsx create mode 100644 backend/ClientApp/src/components/ui/checkbox.tsx create mode 100644 backend/ClientApp/src/components/ui/dialog.tsx create mode 100644 backend/ClientApp/src/components/ui/input.tsx create mode 100644 backend/ClientApp/src/components/ui/label.tsx create mode 100644 backend/ClientApp/src/components/ui/radio-group.tsx create mode 100644 backend/ClientApp/src/components/ui/table.tsx create mode 100644 backend/ClientApp/src/components/ui/tabs.tsx create mode 100644 backend/ClientApp/src/context/UserContext.tsx create mode 100644 backend/ClientApp/src/index.css create mode 100644 backend/ClientApp/src/lib/api.ts create mode 100644 backend/ClientApp/src/lib/utils.ts create mode 100644 backend/ClientApp/src/main.tsx create mode 100644 backend/ClientApp/src/pages/AdminPage.tsx create mode 100644 backend/ClientApp/src/pages/DashboardPage.tsx create mode 100644 backend/ClientApp/src/pages/LandingPage.tsx create mode 100644 backend/ClientApp/tsconfig.app.json create mode 100644 backend/ClientApp/tsconfig.json create mode 100644 backend/ClientApp/tsconfig.node.json create mode 100644 backend/ClientApp/vite.config.ts create mode 100644 backend/Controllers/AdminController.cs create mode 100644 backend/Controllers/ConfigController.cs create mode 100644 backend/Controllers/KhatmController.cs create mode 100644 backend/Controllers/QuizController.cs create mode 100644 backend/Controllers/UserController.cs create mode 100644 backend/DTOs/AdminDtos.cs create mode 100644 backend/DTOs/KhatmDtos.cs create mode 100644 backend/DTOs/QuizDtos.cs create mode 100644 backend/DTOs/UserDtos.cs create mode 100644 backend/Data/AppDbContext.cs create mode 100644 backend/Dockerfile create mode 100644 backend/Migrations/20260603125735_InitialCreate.Designer.cs create mode 100644 backend/Migrations/20260603125735_InitialCreate.cs create mode 100644 backend/Migrations/20260603142557_AddKhatmItemUrl.Designer.cs create mode 100644 backend/Migrations/20260603142557_AddKhatmItemUrl.cs create mode 100644 backend/Migrations/20260603144334_UpdateTrackingCodeLength.Designer.cs create mode 100644 backend/Migrations/20260603144334_UpdateTrackingCodeLength.cs create mode 100644 backend/Migrations/20260603144804_SeedQuizQuestions.Designer.cs create mode 100644 backend/Migrations/20260603144804_SeedQuizQuestions.cs create mode 100644 backend/Migrations/AppDbContextModelSnapshot.cs create mode 100644 backend/Models/AppConfig.cs create mode 100644 backend/Models/KhatmItem.cs create mode 100644 backend/Models/QuizQuestion.cs create mode 100644 backend/Models/User.cs create mode 100644 backend/Models/UserKhatmSelection.cs create mode 100644 backend/Models/UserQuizAnswer.cs create mode 100644 backend/Models/UserQuizAttempt.cs create mode 100644 backend/Models/UserTotalScore.cs create mode 100644 backend/Program.cs create mode 100644 backend/Properties/launchSettings.json create mode 100644 backend/appsettings.json create mode 100644 backend/backend.csproj create mode 100644 backend/backend.http create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f1a249 --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# ======================== +# .NET Core +# ======================== +bin/ +obj/ +.vs/ +*.user +*.suo +*.userosscache +*.sln.docstates +*.userprefs +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ +# NuGet packages +*.nupkg +**/[Pp]ackages/* +!**/[Pp]ackages/build/ +# Rider / VS Code +.idea/ +.vscode/ +# App settings with secrets (optional but recommended) +appsettings.*.json +!appsettings.json +# EF Core migrations snapshot can be committed, but we ignore the actual database files +*.db +*.db-shm +*.db-wal +# User secrets +secrets.json + +# ======================== +# Node / React / Vite +# ======================== +node_modules/ +dist/ +.env.local +.env.*.local +# Vite cache +.vite/ +# Build output of frontend +frontend/dist/ +frontend/node_modules/ +frontend/.vite/ + +# ======================== +# OS generated files +# ======================== +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +Desktop.ini + +# ======================== +# IDE & Editor +# ======================== +*.swp +*.swo +*~ +*.sublime-workspace +*.sublime-project +*.code-workspace \ No newline at end of file diff --git a/GhadirContest.sln b/GhadirContest.sln new file mode 100644 index 0000000..e413ff0 --- /dev/null +++ b/GhadirContest.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "backend", "backend\backend.csproj", "{2ACC88EF-1841-4049-9CF3-BCBC68FB248E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2ACC88EF-1841-4049-9CF3-BCBC68FB248E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2ACC88EF-1841-4049-9CF3-BCBC68FB248E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2ACC88EF-1841-4049-9CF3-BCBC68FB248E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2ACC88EF-1841-4049-9CF3-BCBC68FB248E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/backend/ClientApp/.gitignore b/backend/ClientApp/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/backend/ClientApp/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/backend/ClientApp/Dockerfile b/backend/ClientApp/Dockerfile new file mode 100644 index 0000000..b8656e1 --- /dev/null +++ b/backend/ClientApp/Dockerfile @@ -0,0 +1,16 @@ +FROM node:20-alpine AS build +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +FROM nginx:alpine AS runtime +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/backend/ClientApp/README.md b/backend/ClientApp/README.md new file mode 100644 index 0000000..7dbf7eb --- /dev/null +++ b/backend/ClientApp/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/backend/ClientApp/eslint.config.js b/backend/ClientApp/eslint.config.js new file mode 100644 index 0000000..ef614d2 --- /dev/null +++ b/backend/ClientApp/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/backend/ClientApp/index.html b/backend/ClientApp/index.html new file mode 100644 index 0000000..f4c07dc --- /dev/null +++ b/backend/ClientApp/index.html @@ -0,0 +1,16 @@ + + + + + + + مسابقه بزرگ غدیر + + + + + +
+ + + diff --git a/backend/ClientApp/nginx.conf b/backend/ClientApp/nginx.conf new file mode 100644 index 0000000..8e5fe64 --- /dev/null +++ b/backend/ClientApp/nginx.conf @@ -0,0 +1,38 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; + gzip_min_length 1000; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + add_header X-XSS-Protection "1; mode=block"; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://backend:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 30d; + add_header Cache-Control "public, no-transform"; + } +} diff --git a/backend/ClientApp/package-lock.json b/backend/ClientApp/package-lock.json new file mode 100644 index 0000000..c143f6d --- /dev/null +++ b/backend/ClientApp/package-lock.json @@ -0,0 +1,4763 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@tailwindcss/vite": "^4.3.0", + "axios": "^1.17.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.16.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.0", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^4.7.0", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^5.4.21" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.0.tgz", + "integrity": "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.0.tgz", + "integrity": "sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.0.tgz", + "integrity": "sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.0.tgz", + "integrity": "sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.0.tgz", + "integrity": "sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.0.tgz", + "integrity": "sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.0.tgz", + "integrity": "sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.0.tgz", + "integrity": "sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.0.tgz", + "integrity": "sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.0.tgz", + "integrity": "sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.0.tgz", + "integrity": "sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.0.tgz", + "integrity": "sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.0.tgz", + "integrity": "sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.0.tgz", + "integrity": "sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.0.tgz", + "integrity": "sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.0.tgz", + "integrity": "sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.0.tgz", + "integrity": "sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.0.tgz", + "integrity": "sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.0.tgz", + "integrity": "sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.0.tgz", + "integrity": "sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.0.tgz", + "integrity": "sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.0.tgz", + "integrity": "sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.0.tgz", + "integrity": "sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.0.tgz", + "integrity": "sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.0.tgz", + "integrity": "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", + "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "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/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", + "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.366", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.366.tgz", + "integrity": "sha512-OlRuhb688YTCzzU3gXPLn6nGyd+F+53INE1qaKKlu6kETErE8FYsyDh0XqXEU+uBRn0MpCzz2vfNwORhkap8qg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.1.tgz", + "integrity": "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", + "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "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/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", + "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.16.0.tgz", + "integrity": "sha512-wArC8lVyJb3+jM9OpDyW6hLCizACWkvQR/sSGqSs+o5uEXEtGlqdZ4v8hENR3Jad6i+LRkK93q/+bQAcvl6V1A==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.16.0.tgz", + "integrity": "sha512-kMUAbimWB5FVbF4Bce4bJsiKJWLIUHq/mEG8+CFDnCSgltptBiG5nguducmsJeGKytlCvQud9Qhzpn49iduTlA==", + "license": "MIT", + "dependencies": { + "react-router": "7.16.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/rollup": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz", + "integrity": "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.0", + "@rollup/rollup-android-arm64": "4.61.0", + "@rollup/rollup-darwin-arm64": "4.61.0", + "@rollup/rollup-darwin-x64": "4.61.0", + "@rollup/rollup-freebsd-arm64": "4.61.0", + "@rollup/rollup-freebsd-x64": "4.61.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.0", + "@rollup/rollup-linux-arm-musleabihf": "4.61.0", + "@rollup/rollup-linux-arm64-gnu": "4.61.0", + "@rollup/rollup-linux-arm64-musl": "4.61.0", + "@rollup/rollup-linux-loong64-gnu": "4.61.0", + "@rollup/rollup-linux-loong64-musl": "4.61.0", + "@rollup/rollup-linux-ppc64-gnu": "4.61.0", + "@rollup/rollup-linux-ppc64-musl": "4.61.0", + "@rollup/rollup-linux-riscv64-gnu": "4.61.0", + "@rollup/rollup-linux-riscv64-musl": "4.61.0", + "@rollup/rollup-linux-s390x-gnu": "4.61.0", + "@rollup/rollup-linux-x64-gnu": "4.61.0", + "@rollup/rollup-linux-x64-musl": "4.61.0", + "@rollup/rollup-openbsd-x64": "4.61.0", + "@rollup/rollup-openharmony-arm64": "4.61.0", + "@rollup/rollup-win32-arm64-msvc": "4.61.0", + "@rollup/rollup-win32-ia32-msvc": "4.61.0", + "@rollup/rollup-win32-x64-gnu": "4.61.0", + "@rollup/rollup-win32-x64-msvc": "4.61.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", + "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.1", + "@typescript-eslint/parser": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/backend/ClientApp/package.json b/backend/ClientApp/package.json new file mode 100644 index 0000000..e9044eb --- /dev/null +++ b/backend/ClientApp/package.json @@ -0,0 +1,46 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@tailwindcss/vite": "^4.3.0", + "axios": "^1.17.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.16.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.0", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^4.7.0", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^5.4.21" + } +} diff --git a/backend/ClientApp/public/favicon.svg b/backend/ClientApp/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/backend/ClientApp/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/ClientApp/public/icons.svg b/backend/ClientApp/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/backend/ClientApp/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/ClientApp/src/App.tsx b/backend/ClientApp/src/App.tsx new file mode 100644 index 0000000..5d589e5 --- /dev/null +++ b/backend/ClientApp/src/App.tsx @@ -0,0 +1,47 @@ +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { Toaster } from 'sonner'; +import { UserProvider, useUser } from '@/context/UserContext'; +import LandingPage from '@/pages/LandingPage'; +import DashboardPage from '@/pages/DashboardPage'; +import AdminPage from '@/pages/AdminPage'; + +function ProtectedRoute({ children }: { children: React.ReactNode }) { + const { user } = useUser(); + if (!user.userId) { + return ; + } + return <>{children}; +} + +function App() { + return ( + + + + } /> + + + + } + /> + } /> + } /> + + + + + ); +} + +export default App; diff --git a/backend/ClientApp/src/assets/hero.png b/backend/ClientApp/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 zcmV+cGycqpP)V|)f$;Qooc7=_G zlYe)HToTQIc!$)^+J1M1y0*T%w!p~7%ux`!eRhO?c80XDxKQ*R^lUUMnA>6NT^?feoZ8xxvP32D&s-9ow zqjcM}eesrC)NeDmsf)*P7wJ|K!&xP%Zy4iI8lF)Tv2!reW)tCzg_1=PmOwd1SQfxa z8;58t!=z~Ba7CYlNWVG>he8aRPY|+-JmozNhn!#9i#77Aa_Edt$ijyCWL#=~I>~2X zZNrQ8I0=D+NWD4pq=7~(i zhfThMNw|G>g^y9pGzxX7ZSApl@tIxFcs{p#MX{Ax&XZT+cR#U+OWc@S)pkIuI}dzu zH?^Q=<(y&Vq-oxSLfc0Zmq81bjZWf}RnssBaD6}2g-XJHLcN_|*IOu>m|x$nbm(?E zyNy!Zp=RroS;?Vg*kmoJYBi!n5{_^@rA!)=t#a^;N$8GL!*DsQb}`yvEuX!G@||An znOfUZAevPrkV_qjl|<~3QRZzG&h@C9Y5z zqpNH4xqbF_InIPh)kX}Vn^5kyed|mOuq+2>M;v~KO37a#yrEn3XDqtOl=rc6_KZ!; zreo)DFVB4|>1Zd(bvMI%8uM;3!)YMYu&cG?(PE!B~y@3yKBMt|R zAf=I16tFwPsl)!jDqvYkLHaAQ+f@W1m6F5aZvwhm4JL z{_l)@b;)mDSzle2gyFP5-r1x-5X{G}ot%VyWP@vEW80!Q=f%RTfpg>B*TA^pyWYUQ z<=xPtz}WcZ!;rFl4m1D&FFHv?K~#9!?A%+fn=lXt;9!Fc#kQ;zk~gZFsH z8e5iu@c_pzX&qb8&Dum*oXwB+fm6l6gFfC|o*wgEiy6tw~&co z9Vd_4)P%wP-KwQW7|lN-znGK#?N+j24U=$982myIBM+vsiKsc*@4-rwJxuAaHKna6 zT3wi!C~a4ZKH03qU}_1bKyx0&$CaK7_%Z+Kl$)fF5^op zZApQF2TvDav!s|krTjw-8US6ep z%!VmX4luub+fseQz_D9ATJQ?iQQwD}TZz{-yo#l12a%+7bT@E(X-hyaVS-5vuXc#^ zx^w;L21;NphGVoj*{s3f4dme0y2LC=G1-7THd`#z?;tuC{^9k(dM{Rf2GOxg7Jzho z7nSZHl7?M9kdalX`)YgoKEfiae5+;$(OGeN1eqxrv!ZCVKyH>xiyNqfe8xzY8*7)H zQls8KMp)F4D>ED;idMOU^^WhVF@q>ZSmeB0y~qC~|DB648hr%Sh|*T(4q|w2l?m2+ zvBVw3@7+Mz?^Yc#+se6KM;a<=(W-I>k)$-qL2V*t}VaW`;?P4)WqI%maIDq8!oUcSYAD`}wWjkSyAVsnF65#2zQ zZ>(K*TlS(E#4y$4Zq+e^_&}d)q20hCe3!LfLYP%nQpLJ~gM6a1hJlz3)aS<9C9me| zAcmJ#>tOwBy{HoP0Sm1&_(E+S@6 zgBIFUoei8zJmdpiq8q5=OY7t@`)JWxn_&GvKVr=Zdb_pEL_j|=?f;WK^U9Q0efd#K z9q7SfJTl4pmA$jsZ5oK8@O9#!I3Cv-kL)<8SalSsp#dcpvJ}Nz#G6FC0%9|7Fi#8; zGDJXtj!&GljT3*HE@0EE>G8Se&d)*nkqe}-?`3vPl&UqK?xG z!3XJ4M-x`EuQjhBbu?ik-)rmIt=DF_N?TVMP)8Gjn)TZ2V%H|zENbeix}kOxd@0}Q z>)HuH6Ean!uS#~4g2Ne2WsMGel|h%j9*W_quQheG^JqmKhc*RYzp0wKlGjBq2VzY_ zgOv8WC1+%W=W)k)Yp_`8kfE=uiiwOZTXi8Uj9YGr$f@yJcJ;#&-Nq~sJ7anE(@;QN z=~br%7%7`isKStX|7!1?L(apl^QvPKlrHV4S+6tNVQ*R1iGdC~WMNE1$a+=rpQmcB z>wxiLIBvOnm;u*;9Y!kJdy(T4lk|8>JAm(&wEsFIF1$_*{>2ZNd$V6DS=SfrGxAv0 zzKe377JI`&o9Ljr+VnS*EwehA{f&{cKZF(6*MG5!p5MvrFA3ll{fmRG*L@6^cb;o^ z3Wm8c?Sc6$`>~VEWw(c$Y?nRO;2Q$=ulpqPtM^=1IZx;@xK0PgO7rKQ^WHVLwtgUT z%|JF{^f(VH)wLKQ%dYiu2RmchBdxL0-M?wxxul_z*{h6ZZ`>-k(vizs((vW8Lt6Z6 zY;Dt?@JWyN`O`f;&d1Mb?e%9oyRK1ql?EE5XB2(W)|D1~Rx35$H6@6)$F?)7V|zEO zI}fu0-0}8W5=6sg$fPnZ~7=tTudl?Ecb@pxbo)vni%gP-?hL|%*?62C;x6?@E`VRnJv z?fTb;k4x;TS7Cu-z%J}uy}e-pwpLQ17Q@4DC+FCdAmNKklG$`I_pyw7E{fYmw~{Fj zi?6KcVy=Wrel)EB_DWO|0CKmI|13!gBV?X`Ozp7x>?6jr`>Qz=^4ea35!$*f}) zS$i+x_k+@P2q1RFUH^ZTTk7=n?cjfR>hTq3l3SY~#w+I8SSutXGyhw;Ws~=zMQ%Vc z>$On~47Ut?P*_!TOQ&PFmLAyJieB2X4_Fd_!WxI-AY`q1Lc-oK?+qcOTzlQ?@~x@OT}*9jTVNfl@3rGvZpWI=eKg>T zZb@6YWz)J=IhP7CF|c?G62vMEG%#U}?#86$0jR4sG~i(jRd#jmn`7b(O#?N;3a;1t zhXLssmUwGhp79luw#(*V8WL0|8+E z6=YZ_O@er~$LrD_PYGc(kJgB=;yw#+Z3X6LDUZ(NcwN=B-hjdiHm!JFar%m{(5bEW z@@_VEtG$5;`EJZ|OkJ@l&G9n((w@uNFwmU%bG|s#TbcJJos!{e+bjCjrCq_}LcN!UFgKtgg7siV*7# z!}1whTRRi*-avJPu->C}Z8EiuK$#886+H_#_!btv+rsiBbv2jAJvJ+O0{#}y(%L3H zfjU-kq_-L@2XrL*ae{{qYJkD{@dw%*bkh2P&YS-0!Xt!PRz7KHV0+~j(t9W8lAVWR zt@B*DgURgEz4>WuN>o?_iKcw$?k{||Pg7{Q2o4|VmJ)mg?{VQJA<}zEr^YAAS zgGm5RT4T3p)U;yz-tfBO^kw8?IoG!IVmc+Z3m#}AOQ?5MRa>)OcU!$N^_+yK6ayn? zK>~WK0!#ysuj^oNLakm)Zvu+J)OSubX^kv!c*xgdIvs;kln!rgG4*uZ;w0mQQO4XD zO9P{GNdv!=cQ(CAL{S(%KtuV^zC&Q{%g)PoXnp^gn^>c*`E>$hLYg2HjnbVGtWLa{7zHdG1jT@B{|Dm16 z7K2(jsfG+m*Zxof)iXxu+!H5Mo-0$pkyV3VV4B@Qms46M zuBxGRV@HxU7Wwx-6CB zaU*HO<_qn$5GH>&@?nRy1{z zkik!sLfWQ)r#75)vVwCBU*r_)Q6mp?!j85{#Xqse)ApRdE$V0%I0*~e(_{)5H)`Mk z#rExC>yjhZxuL@|+#v4#<Axw$+VpV zuT;!2Vww$je$DpAW`$FX_Ab|Ip%$;&T$-lW8jS~B$>G}rd>eQG+$h9lQx4Mx0w={m zx9?T6VU`>sR}XClkAhHEShOUe8awiq zmizhL+}5UKs3}6~It7vBTig9dfQ2Q8coo+Miiaw7n~>4ybv2Ptt0^^=VqX(t*Yya9 zr`FxxFX8(v*H=+uJ#JJWIB2A(==HDYx~^zZ2nu?2`}|Wsa*f3h3ixc+U|FDtAG$Y! z*lc_7se5Oso-Cgqe0){{!8H4g$3<8!R<6JOurD;((({c$1(pwb>(#TT!sge@4>r2@ zVL7>U`0`nsWAYErezk4(Z!gMI2?UTo{J3Ajo(u4)KYIRd>BRcG4BoS3G0EXyEp@tw z%P7__?A^a>Q&AKL@ayDO9D*Qkc!NHnO9l}kpp_6hXbMppYL(X1L?njdFT|-h2<_$; zAtDZ!1Rf%|yb!qbWKd}%0b`LzBeyNy43|QO(&h2mxQLUL)|0%agVOW)6TV!&Ip^Ls z`PG2cygM8)IecQx=Fc+nqYRo4hS^^-nM_&-y8?EJXUczP=DIw(GkTJdpEdh<_STs{ z|A)4n1GKdE=Wu!!nYoZHcUQ4S&R;oDOKX2lrkdF(mK>hz<$Pp>igjOcvoRIjlN=W8 zu8Gx5(roqn8$>gEE5vy{GiGeW8Tq{vnf3hS-V=$tZkQuftUVuU8o6k&dn=Yg3)6MOIH>nlK^-2+C6BZITr~1@So?NvG#TwL)|~=1YXGMTLpS<)ziK_CSOabe z=cB#5)yz|@0i9dSo?*CX)}UP=s6)B+F@~Em(u@Q(I9J9i_V{LmMu8BfXYMh~*oPP+ z!3~xTv|(>|=n6ZOtT~C@V!z!w%18*8T2t6}U2S##rC)mekBql&VsBX;$~ByGE$oA9 z`0Wzq8p?R{4)$l*on;!cLa}Dh^Xe?owiQZt9nH1fxxh$pN9K%CtOw?u3>85L7rr!d zXs)l{TZ{xXP&U8exz?9cv~dNNibOmt*K4I$?RxqIBZ0(?Mg-9FS{*9Bc49Qc1`=sIF-rye`aNT1G@4NwXcnyc@+bw_mTsR>5< zF<2;X0QesG_pw|TonqVBhRtfqI>ty(SIu&VOXd0CrLlfp+;WH7HYjhqnu^oAY!9cB z=B6#R?Rfz9BP`dJ=@v_?70s3HxQPk+{6Y+lM85f2NF^00*^OcM0~?JOZfR9ZPYF+# zYSs}(_BUYV8{n@2a1hD^SV41bwmi2uztR;PeBgF1F-`9>`zoNss-@3LaF2sjl~>OaaVmp7PNp+UT`6@}gR%uzqHDVeEZ14{Yt?n%JeQm+t(1_u zSc}oj^{b;+rlS|ME%+LjzSI&xu0Bblxo$MJ-J$kJ?Qu_XUXh}*@*-x@ny|}wVM%Lg z3tNB`yvr*}N?ClGL;H2cglcvErIccU3(eP7>@~4nOIcI~-`P8tSQnx=jI&{9)!1}l z;gQ%_h>ZlPSV@o@Azq1R$C6ja5!^ZGh;YRhhxs58qJWo9@Bceac&yy(pET1hnn`~7@}2L0&dfPKYs$ih7m2}R!25!(hxqA(!UIw; zK4+~Jowy3=RNC6nE=ncU{LH5?*9@W24lacJlvCZXB$CYtE@>c+~H zkV=(5I&gb{xn2!~f&fs2NQgAL6`p|kyt6kpWk}iVlqIp(H;ig`{_U9yxs1jzu^ETM z7~)Rg8C-NueqTYP&U8l{DY=Y47cR zOR@U%$KQV{mkRF|4)z9Y^t3K`@p>duY&QLUFeh6VoV`a`$U@)(z!-N*5Cj<11$EZW&hJLX83TO{lJYP74rlDZQPkm@t<=U^I)x@|UnHHkdQlh?!ltZwl92rE;;^ zZuIappj4dhld1}kttYYV-j|KF1Kus zWBnzttD^00%LFK(wrwNragFub6xiV8QE2rm<`&fcR4SLFcdtLxVuN!Aal-g6dE4%k zARZ}|xeo;K{0yf7@9aua%2j5o)CPcIOc6uLHFJOcgtB5owlcNAwyAHc0QB0Dts?c@ zUemG~j_E&W7R%+x-IO4FJl8e&*2Blmp1S#RA|)geVrxvP)NHdYuxi~g&Etn?QdNK8ZDKZ?QFLU?zh30G|t9G>a_X4zk}Ygw<^$7K!GIn(Io$>(d4ODJQ2XSd%jpK zm7>ptl$a3GyB}5-%p4>Q*p#VL^B{yQMuFCM^#l#+N!Ne z5_PrJWB=@Iy+t)H`g1lX`{bm($KE5I?0c(JEYm#t{F}j!xtsbob0{xu@0TB_*>G7w0ICn zr#VoBktqHZ~XxhiKD*lcG|b;H*|Ny3P^8ceV`sfBRfrhwZ!T+MFZ!F1Bt{q$8d9i6o?~ zODj^POr}&ivSa^R^YFIq7o0giLBKCycH_aU`F6)O6JX%nPTwh~Q`eq6*0iE#Srj2^ z*_hN3%*b83zfafy60@Cp3{J({RlSaEn&E?mrxRNC9GQ7#+f=s! z0KBf-9Ny_v2VbE%aB|Di)5kNJ^t&C`4D(>t7zYUWUFtbxt+Oq=!@O7BU)}>d*R72o zFF)3jQD_lLe4is&xzyJYC1-c{8TX$RU>&>P$%)ufpez0XSAukmh!xcekg`s$c<>-q zI#zn^JU0zzF}V60)o$_gY}PQH>b2M9&8fRZa#OauglPb zeQ@pMm&=!vNgos4CluQjLMV!pfkmxK+35bi^k&=k>9h02?l+u+m0agG;(h2|Jslc-llvtEwn~*w3bx7qnvZACG<8}AGeaDVvcHbKd2>3G^ zSFPULUn-?Pmo^-_`mLZr??uNH`2=I&yajlrF{DtUxMy#Nu}z=3y7qbUA;5`)hibMR zhXL@@uKyV0-2&A@t@!xyrBnMJl&^o@Gx$&5_q6?D=ji5grd-~=?dlg;ur(_V0wjh! zA=JV^C1m+DDkOsgr<%O9ZQFg!0}pD(#PSz4Dr_EyS5$`)VIAv);4n-SFP~YtC7sH= z7&*MfpH;gd*FHbkmD#)hVxb6xjc9~`t?_{=JS+@ip_cTicXxG<=7m9& zPX+Z8IC*GSAXuGCrZDHgR$r%jyk-fctis2Kx4HvZ|B~8uC@o)m^>Hy-O!&TKA?$&n zkP2Xc54w~!=z2?^NafyL*L0V9cbYrugHBBUj`xVyZmGFR&kvk#>1J*Z~i zNTz}?IAdJ$gkqd2!Gw(%LzE!O5s4C7q4%T~e_P{+z=DNDKrG**p=U`d5yg^vp`;Zn zsU=8gd0a9s4s0FPJePWR9eH5=+O^Kks&kC-iblNqTh2&Pw*^(4384f+D8N|fewZu_ zg2ejQ)ov;ztz;NQl7yj;A`(!H!XQu_$sqY9h_IrH*}_%1{L&_YLDvO?%R5Z-t+ClW z_qERbL?HKUZ!nt+!E9S`uoh^5A|DaIHe*_gf1`E_Vq+}{&T@t$EGhMnRjJ4z2w_W8 zp+qjs7as22^&S3wY1?+}^j-I=RcCE>#|39)g(lU7v_8;?=qK(9D8-*pPdiy)P3lIblG`+?%ea| zYoD3dopYt!tKgFicfNmNi(EWE=E4hC6(r|PYtanqJlmt57YOVrr2^tfrG(eG9C##X zu&1t@%L$RIvpj!wUA z8i>Pqot#_+Cnp6L2XPcZy1ar|9MnY+7eNvK1E)@Tr#2KsXq1*>)uUCozT7L##ok?o zhA6ofP4E|b*9tAfG?uf$#}>TIR&1A!yslP8}i7w-EzW(x#9VEvx18k%Tn=-$VV zkOtUr0b2!w3t>h?#8AZl^Az*(6KCGlD;4j~yx};`#2gN1_gv=%7KVzecIRakN{f*4 zeaI>yH;-o4OGhvGTU)(quWI)-q?V*(sVesSMv|wMUQ3hLEt=lBB$KZ9TyHr>)f7o%) zPYeU<3P)*P10*7vE)nA5#{c=6-E-_>r_u4e3i!I2+UksELwDqwMeBZ9FSP$;^Ajro z_@M#_Ss$?ejoB@!wN|kbGKs(0zLo%0QpQXW#t;oC$B0MZYZ&Ej?8~fNhcCVvPo3vo zFn0WWZaPliF^8_}yzb`*f@yg0uWv6HgNI)xa=pO%Ck(C<=-60l#uD3(wXP~c7!NoX z0&^6=N`zcc90F#qt@=Rn@r!3(*1v(Tl{B!m?Mc7yIA+nEHpY{YWr$=)F7rhR1P}(v zt{YhY#;jsW6G>#xhP*B`OCk|Pf+NN;ju1rxa*HAgoGq*rvqw&xe~;t1JA31$s?GBb z*g7&@cbKo4n<`>)!UlIAgR6q&))B0KYU8r66GbFj?8Guw4E%&}Qi_lT003LtoIZei zwD~=XZmeo+yZ2Pq3KYCF-R&11^p= z@H%s+=G`}wrbJ{()Mh71#2SP3Zy3m>l1n?0N-N1Q;z6?oSxr-G(H5m4EO>~&;}VKi zfY}3w+9z>vp#d)hVuu`)vG_aaH%3b=WKMnSu&c31;<3O;bz2iD=w+o4#oBb36 z5ZCF*Gu?zjZIR0S>_%pHY2$k8D^n7Sz_K8tCDeXM+dO<#LSg%h6`~dnVG1N@T7v&e z%wEd1!k{^zfz_1BTW{!$!B%g)J^2b87!9Y>>100X1SgT7s0z$o>^lAA=Gp_cC1(h=*5Tmf8z&LGJJ>$|K^~s`z9*OWz5MFUr?>Bi?_PGBB)#psD5?>n+q{o_ zz7~ez&;t#h8l$jwGPCC&xq2YetXYQT+0F3j(`xmNGf8dj#an|p#I*pvI*kwW4iuB> z+q3_7xB8y;pLzHG-S%+UHQA zvqp;$kmGJY>lLsN4C~&TcvAS1SErTcwcw0r@wngk zShAUA1M9b#g}^pL-zH7Q#z^&j#r9F8BTVfkR&qF<=e35goTu7c|GN)0mokj4m0%~0 zXJ8j4Hc_l;HJ&uU*Iw`8d_EscJ``s0tk9mkKo^&#TYXm-EoAzTQObxa@^u~g2t#T) zJz|rE!I_?i4dCJC=B8(_pZ{YR>|V?0iCcnU;E@$239^x?SYCfNaMHN;CtHIS_zHN9 zTkQc1v@O35okiFtq5_u+5FkY55ap@pi)O?}x0D1c*qB0KpYR}>Ul+B0Vmr}Z@+%mJ|As}sis_=ROPbov@*2thpE&?!V#Qgu$snYvCZ zrkhmkMU+fSf-s8(L37fPr&M*jRs{{THb!aXQu|P9l_-vJhHvLzMGH zE?1U0H_+PmNABp9`|KzkGfrrZ%XvdGo6*<{d5m9~L7 z_^`M;X6xDo=m6LY6RfvJEvsTK1!u8d2HPx|$S}p;sRy!I zWL55Yxu~_B`OP@~(q6&W3#)~I&+MGL%GWR$#udC151^wsswhqlii;rP9jJpiI7o&Z zAb})=HY7?4HA|re3ns`%$)FuvKCFWjhb~?IE)F6dF2K5}poj-NK6Gf;hw$t3=1txY zoxQxZWrQU6K!%|~!m?~Bnw-6Rr!F3BZ{u5!LqnZTDON}Coj9^@&le)V!NYrVwS~B% zEL+>Sr@}qGwGvu|HrOo|gSt__ezN^&%~{*)a=rf7y1HujUcr`zZB<4#l@T#eN)si} z)lZA<{=tKx8E%c9>A(##6}_p+~EZpKsl5a4pj`E*;_-6`ysiv zffA!7=MT1vCz}-m4~tjVey1b2KSR4OEtLd-(_DdUqYZ74LaDkhH?KFh?%WAOP2WbX zp@zT+Dx|5_f%JQiAGvVw!oh+g3e50u!aPfMxdC=E)XB{F5IcEZhePIM- zph6Y`$Oy?JBL<8Ex(SqEhLeQ@XcrdA>a?rx+_~HLA;l14)WmmpH}_w?Pg#HBZs0eS zwypwAW?M-x+3AU-(GGWSJ=ngxUEcEZ5OsX(Qlt!MQ zn^(`S{GHkAv(8@D`EAfSYig%Cxv?z!{=w^F#y)5_d7FuKZH7qlR-#5B0bt806%D0I zT7VdVP_?q*%Rq8UR;JkD4i^RXowt+E%#V2U>TfDqzZSDZ+dR!a#T3I>-z_$q9@k|m zy5~A*m~&JWP@E7a=pc}4kVHTc4h&R;Li7d@f`|hKMLkbb^uhOakNr3&FLjlm~i5NBM< zFaYI{;cpiHCNRdE0dg*>qIm(_t?#$h=(SCw?h3rJV2*ER8{O4^3#=dO)KwklZkoqU zS8i5c%YL*y*4;FY#D=XmkQnYj%LH)?02~gSJH`Qp1XY64g>%c_K$xseI&|e)7vRoL zAqRba$G@%fSGA7X7hQk%_3NVOYVS+$leU_!&6*5uN)8#5ZBz_6ASCA;azYS-Rt@ki zg2NWz(=;t}SC(~Ibl63$5C8FPmhXqb^)5#jaJ~I{Ex3xZ!+2h8$}}h_g@Be>HZ;72 z6#y#>AY3^skuVKF#0WxFBQ()5d5_nWb?c6c>EeMM|Mh+*&wEpPyxHCq{R-Gdr-`hN zF=1sxl&mBoK+#qRLl9#CEN|Fg8>nbmsTg3a1;#M9enQ$RgWk}kp#-5wh=EF&1tl%mJln2V^8o%Qv(*=zEuO7y z=m*8?xpUn-*@h5Cl_3BK3joiGkyaScK+>|MWdMRWm@RT!Q1piAlv5hL@B6>3&GI8) zP!xBc6}ZNIpJLL%2a8Y!+(<=f%WX>_uWVxlga9!D*oYt$l0cxRDMvqfU;Kq_mLK5k z)dvqYcgLa_Lz?3HyeF)@$%$&6lI?r4I>6W#M*<)vq{?&Oqrx``d`mhpVPr> z#q078F6gw_X<=?KR>8%^t%@wbITvNMu!hKiTSkCTJkw>1!e*Y{%31#_yMf=LW7{RJ zYoC^w$6%3cBtVG5)x#{Hg6IVTh9XEcM{gQwXk!R^y95^f-hZ`d{aVa+xW1EO4wDV4 zB?JgD7*?qkvc|$nIykTvNl2x0j3Q!MXoLL^)~}d7jcYf(H8D~c+?$pKL(px>Z3`eb z04RzS6_AgFT6Pn#iZAg$Sl_j8#;6ShF%&(Fag#E2asU@@LaN;=b=Wf7sgPKhfzhBM zC@eFL8^MrnA*9&Khe*Ab@CC9*uyJGXyi(;y2>lQLJZt;ShtJi?3Yf_t`F+$hY!+Q2Ndsx=U+bjTiAy7djLji>7k%k`$9&--f<*BNA3Hy&ZrHH|4 zG5H&9cB?O#zI1_OOf0Ce%mDfQxdtp3vU%(iY6yji3iISS61XLv#z|!zI_sZqza@B+ zyu9st5-h+`H7QUKx9}3w@oU@EO}&cEzG?fu!!bLO->%zkcg;i9^j`S~=WKMnDi1f= P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/backend/ClientApp/src/assets/react.svg b/backend/ClientApp/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/backend/ClientApp/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/ClientApp/src/assets/vite.svg b/backend/ClientApp/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/backend/ClientApp/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/backend/ClientApp/src/components/ui/button.tsx b/backend/ClientApp/src/components/ui/button.tsx new file mode 100644 index 0000000..9783ff6 --- /dev/null +++ b/backend/ClientApp/src/components/ui/button.tsx @@ -0,0 +1,51 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-pointer", + { + variants: { + variant: { + default: "bg-gradient-primary text-white hover:opacity-90 shadow-md hover:shadow-lg", + secondary: "bg-gradient-secondary text-white hover:opacity-90 shadow-md hover:shadow-lg", + outline: "border-2 border-primary text-primary hover:bg-primary hover:text-white", + ghost: "text-primary hover:bg-primary/10", + destructive: "bg-error text-white hover:bg-error/90", + }, + size: { + default: "h-10 px-5 py-2", + sm: "h-9 rounded-md px-3 text-xs", + lg: "h-12 rounded-lg px-8 text-base", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/backend/ClientApp/src/components/ui/card.tsx b/backend/ClientApp/src/components/ui/card.tsx new file mode 100644 index 0000000..9eb6f83 --- /dev/null +++ b/backend/ClientApp/src/components/ui/card.tsx @@ -0,0 +1,69 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +Card.displayName = "Card" + +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/backend/ClientApp/src/components/ui/checkbox.tsx b/backend/ClientApp/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..47e5a98 --- /dev/null +++ b/backend/ClientApp/src/components/ui/checkbox.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/backend/ClientApp/src/components/ui/dialog.tsx b/backend/ClientApp/src/components/ui/dialog.tsx new file mode 100644 index 0000000..021047e --- /dev/null +++ b/backend/ClientApp/src/components/ui/dialog.tsx @@ -0,0 +1,116 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root +const DialogTrigger = DialogPrimitive.Trigger +const DialogPortal = DialogPrimitive.Portal +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/backend/ClientApp/src/components/ui/input.tsx b/backend/ClientApp/src/components/ui/input.tsx new file mode 100644 index 0000000..0228fa7 --- /dev/null +++ b/backend/ClientApp/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/backend/ClientApp/src/components/ui/label.tsx b/backend/ClientApp/src/components/ui/label.tsx new file mode 100644 index 0000000..97dea45 --- /dev/null +++ b/backend/ClientApp/src/components/ui/label.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cn } from "@/lib/utils" + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/backend/ClientApp/src/components/ui/radio-group.tsx b/backend/ClientApp/src/components/ui/radio-group.tsx new file mode 100644 index 0000000..b273060 --- /dev/null +++ b/backend/ClientApp/src/components/ui/radio-group.tsx @@ -0,0 +1,41 @@ +import * as React from "react" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { Circle } from "lucide-react" +import { cn } from "@/lib/utils" + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ) +}) +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + +export { RadioGroup, RadioGroupItem } diff --git a/backend/ClientApp/src/components/ui/table.tsx b/backend/ClientApp/src/components/ui/table.tsx new file mode 100644 index 0000000..d7b54e3 --- /dev/null +++ b/backend/ClientApp/src/components/ui/table.tsx @@ -0,0 +1,105 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +const Table = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ + + ) +) +Table.displayName = "Table" + +const TableHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/backend/ClientApp/src/components/ui/tabs.tsx b/backend/ClientApp/src/components/ui/tabs.tsx new file mode 100644 index 0000000..fd4daf2 --- /dev/null +++ b/backend/ClientApp/src/components/ui/tabs.tsx @@ -0,0 +1,52 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/backend/ClientApp/src/context/UserContext.tsx b/backend/ClientApp/src/context/UserContext.tsx new file mode 100644 index 0000000..8a8a55a --- /dev/null +++ b/backend/ClientApp/src/context/UserContext.tsx @@ -0,0 +1,53 @@ +import { createContext, useContext, useState, type ReactNode } from 'react'; + +interface UserState { + userId: number | null; + fullName: string; + contact: string; + isQuizCompleted: boolean; + khatmSelections: number[]; + totalScore: number; +} + +interface UserContextType { + user: UserState; + setUser: (user: Partial) => void; + resetUser: () => void; +} + +const initialState: UserState = { + userId: null, + fullName: '', + contact: '', + isQuizCompleted: false, + khatmSelections: [], + totalScore: 0, +}; + +const UserContext = createContext(undefined); + +export function UserProvider({ children }: { children: ReactNode }) { + const [user, setUserState] = useState(initialState); + + const setUser = (partial: Partial) => { + setUserState(prev => ({ ...prev, ...partial })); + }; + + const resetUser = () => { + setUserState(initialState); + }; + + return ( + + {children} + + ); +} + +export function useUser() { + const context = useContext(UserContext); + if (!context) { + throw new Error('useUser must be used within a UserProvider'); + } + return context; +} diff --git a/backend/ClientApp/src/index.css b/backend/ClientApp/src/index.css new file mode 100644 index 0000000..b9fb7d3 --- /dev/null +++ b/backend/ClientApp/src/index.css @@ -0,0 +1,78 @@ +@import "tailwindcss"; + +@theme { + --color-primary: #b8860b; + --color-primary-light: #daa520; + --color-primary-dark: #8b6914; + --color-secondary: #2d6a4f; + --color-secondary-light: #40916c; + --color-secondary-dark: #1b4332; + --color-accent: #f59e0b; + --color-background: #fefcf3; + --color-surface: #ffffff; + --color-text: #1a1a2e; + --color-text-light: #6b7280; + --color-border: #e5e7eb; + --color-error: #dc2626; + --color-success: #16a34a; +} + +@layer base { + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body { + font-family: 'Vazirmatn', 'Tahoma', system-ui, sans-serif; + background-color: var(--color-background); + color: var(--color-text); + direction: rtl; + min-height: 100vh; + } + + #root { + min-height: 100vh; + display: flex; + flex-direction: column; + } +} + +@layer utilities { + .text-gradient { + background: linear-gradient(135deg, #b8860b, #daa520, #8b6914); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + .bg-gradient-primary { + background: linear-gradient(135deg, #b8860b, #daa520); + } + + .bg-gradient-secondary { + background: linear-gradient(135deg, #2d6a4f, #40916c); + } + + .shadow-card { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); + } + + .shadow-card-hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); + } + + .animate-bounce-gentle { + animation: bounce-gentle 2s ease-in-out infinite; + } +} + +@keyframes bounce-gentle { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-6px); + } +} diff --git a/backend/ClientApp/src/lib/api.ts b/backend/ClientApp/src/lib/api.ts new file mode 100644 index 0000000..7b470dd --- /dev/null +++ b/backend/ClientApp/src/lib/api.ts @@ -0,0 +1,123 @@ +import axios from 'axios'; + +const api = axios.create({ + baseURL: '/api', + headers: { + 'Content-Type': 'application/json', + }, +}); + +export interface SiteConfig { + siteTitle: string; + subtitle: string; + channels: { name: string; url: string }[]; + shortText: string; + quizParticipationPoints: number; + quizCorrectAnswerPoints: number; +} + +export interface RegisterResponse { + userId: number; + trackingCode: string; +} + +export interface LoginResponse { + userId: number; + fullName: string; + contact: string; + isQuizCompleted: boolean; + khatmSelections: number[]; +} + +export interface Question { + id: number; + questionText: string; + options: string[]; +} + +export interface QuizSubmitResponse { + participationPoints: number; + correctCount: number; + totalQuizPoints: number; + totalScore: number; +} + +export interface UserQuizResponse { + questions: UserQuestion[]; + participationPoints: number; + correctCount: number; + totalQuizPoints: number; +} + +export interface UserQuestion { + id: number; + questionText: string; + options: string[]; + selectedOption: number; + isCorrect: boolean; +} + +export interface KhatmItem { + id: number; + title: string; + url?: string; + points: number; +} + +export interface KhatmSaveResponse { + success: boolean; + totalScore: number; +} + +export interface UserScore { + totalScore: number; +} + +export interface AdminReportItem { + userId: number; + fullName: string; + contact: string; + trackingCode: string; + hasTakenQuiz: boolean; + participationPoints: number; + correctCount: number; + totalQuizPoints: number; + khatmTitles: string; + totalScore: number; +} + +// Config +export const getConfig = () => api.get('/config').then(r => r.data); + +// User +export const registerUser = (fullName: string, contact: string) => + api.post('/user/register', { fullName, contact }).then(r => r.data); + +export const loginUser = (trackingCode: string) => + api.post('/user/login', { trackingCode }).then(r => r.data); + +export const getUserScore = (userId: number) => + api.get(`/user/${userId}/score`).then(r => r.data); + +// Quiz +export const getQuestions = () => + api.get('/quiz/questions').then(r => r.data); + +export const submitQuiz = (userId: number, answers: { questionId: number; selectedOption: number }[]) => + api.post('/quiz/submit', { userId, answers }).then(r => r.data); + +export const getUserQuiz = (userId: number) => + api.get(`/quiz/user/${userId}`).then(r => r.data); + +// Khatm +export const getKhatmItems = () => + api.get('/khatm/items').then(r => r.data); + +export const saveKhatmSelections = (userId: number, selections: { khatmItemId: number; isSelected: boolean }[]) => + api.post('/khatm/save', { userId, selections }).then(r => r.data); + +// Admin +export const getAdminReport = (password: string) => + api.get('/admin/report', { params: { password } }).then(r => r.data); + +export default api; diff --git a/backend/ClientApp/src/lib/utils.ts b/backend/ClientApp/src/lib/utils.ts new file mode 100644 index 0000000..d084cca --- /dev/null +++ b/backend/ClientApp/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/backend/ClientApp/src/main.tsx b/backend/ClientApp/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/backend/ClientApp/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/backend/ClientApp/src/pages/AdminPage.tsx b/backend/ClientApp/src/pages/AdminPage.tsx new file mode 100644 index 0000000..5a1bfac --- /dev/null +++ b/backend/ClientApp/src/pages/AdminPage.tsx @@ -0,0 +1,211 @@ +import { useState } from 'react'; +import { toast } from 'sonner'; +import { Shield, Eye, EyeOff, Download } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { getAdminReport, type AdminReportItem } from '@/lib/api'; + +export default function AdminPage() { + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [authenticated, setAuthenticated] = useState(false); + const [loading, setLoading] = useState(false); + const [report, setReport] = useState([]); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + if (!password.trim()) { + toast.error('لطفاً رمز عبور را وارد کنید.'); + return; + } + + setLoading(true); + try { + const data = await getAdminReport(password); + setReport(data); + setAuthenticated(true); + toast.success('ورود موفقیت‌آمیز'); + } catch (err: any) { + if (err.response?.status === 403) { + toast.error('رمز عبور اشتباه است.'); + } else { + toast.error('خطا در ارتباط با سرور'); + } + } finally { + setLoading(false); + } + }; + + const exportCSV = () => { + const headers = ['ردیف', 'نام', 'شناسه', 'کد رهگیری', 'شرکت در مسابقه', 'امتیاز شرکت', 'پاسخ صحیح', 'امتیاز مسابقه', 'ختم‌ها', 'مجموع امتیاز']; + const rows = report.map((item, index) => [ + index + 1, + item.fullName, + item.contact, + item.trackingCode, + item.hasTakenQuiz ? 'بله' : 'خیر', + item.participationPoints, + item.correctCount, + item.totalQuizPoints, + item.khatmTitles, + item.totalScore, + ]); + + const csvContent = [headers.join(','), ...rows.map(r => r.join(','))].join('\n'); + const BOM = '\uFEFF'; + const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'گزارش-مسابقه-غدیر.csv'; + a.click(); + URL.revokeObjectURL(url); + toast.success('گزارش با موفقیت دانلود شد.'); + }; + + if (!authenticated) { + return ( +
+ + +
+
+ +
+
+ پنل مدیریت + + برای دسترسی به گزارش کاربران، رمز عبور را وارد کنید + +
+ +
+
+ setPassword(e.target.value)} + className="pl-10 text-center" + autoFocus + /> + +
+ +
+
+
+
+ ); + } + + return ( +
+
+
+
+ + پنل مدیریت +
+
+ + تعداد کاربران: {report.length} + + + +
+
+
+ +
+ + + گزارش کامل کاربران + + اطلاعات تمام کاربران به همراه وضعیت مسابقه و ختم‌ها + + + +
+ + + + ردیف + نام + شناسه + کد رهگیری + مسابقه + امتیاز شرکت + پاسخ صحیح + امتیاز مسابقه + ختم‌ها + مجموع امتیاز + + + + {report.map((item, index) => ( + + {index + 1} + {item.fullName} + {item.contact} + + + {item.trackingCode} + + + + + {item.hasTakenQuiz ? 'شرکت کرده' : 'شرکت نکرده'} + + + {item.participationPoints} + {item.correctCount} + {item.totalQuizPoints} + + {item.khatmTitles || '—'} + + {item.totalScore} + + ))} + {report.length === 0 && ( + + + هیچ کاربری یافت نشد. + + + )} + +
+
+
+
+
+
+ ); +} diff --git a/backend/ClientApp/src/pages/DashboardPage.tsx b/backend/ClientApp/src/pages/DashboardPage.tsx new file mode 100644 index 0000000..fe954ff --- /dev/null +++ b/backend/ClientApp/src/pages/DashboardPage.tsx @@ -0,0 +1,481 @@ +import { useState, useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { toast } from 'sonner'; +import { Award, BookOpen, CheckCircle, ClipboardList, ExternalLink, LogOut, Trophy, Sparkles } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Label } from '@/components/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { + getQuestions, + submitQuiz, + getUserQuiz, + getKhatmItems, + saveKhatmSelections, + getUserScore, + type Question, + type UserQuizResponse, + type KhatmItem, +} from '@/lib/api'; +import { useUser } from '@/context/UserContext'; + +export default function DashboardPage() { + const navigate = useNavigate(); + const { user, setUser, resetUser } = useUser(); + + // Quiz state + const [questions, setQuestions] = useState([]); + const [userQuiz, setUserQuiz] = useState(null); + const [answers, setAnswers] = useState>({}); + const [quizLoading, setQuizLoading] = useState(false); + const [showQuizDialog, setShowQuizDialog] = useState(false); + const [quizResult, setQuizResult] = useState<{ + participationPoints: number; + correctCount: number; + totalQuizPoints: number; + } | null>(null); + const [submittingQuiz, setSubmittingQuiz] = useState(false); + + // Khatm state + const [khatmItems, setKhatmItems] = useState([]); + const [khatmSelections, setKhatmSelections] = useState>({}); + const [khatmLoading, setKhatmLoading] = useState(false); + const [savingKhatm, setSavingKhatm] = useState(false); + + // Score + const [totalScore, setTotalScore] = useState(0); + + // Section refs for scroll navigation + const quizSectionRef = useRef(null); + const khatmSectionRef = useRef(null); + + const scrollToSection = (ref: React.RefObject) => { + if (ref.current) { + const headerOffset = 140; // account for sticky header + nav + const elementPosition = ref.current.getBoundingClientRect().top; + const offsetPosition = elementPosition + window.scrollY - headerOffset; + window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); + } + }; + + useEffect(() => { + if (!user.userId) { + navigate('/'); + return; + } + + loadDashboard(); + }, [user.userId]); + + const loadDashboard = async () => { + setQuizLoading(true); + setKhatmLoading(true); + + try { + // Load score + const scoreData = await getUserScore(user.userId!); + setTotalScore(scoreData.totalScore); + setUser({ totalScore: scoreData.totalScore }); + + // Load khatm items + const items = await getKhatmItems(); + setKhatmItems(items); + + // Initialize selections from user context + const selections: Record = {}; + items.forEach(item => { + selections[item.id] = user.khatmSelections.includes(item.id); + }); + setKhatmSelections(selections); + + // Load quiz data + if (user.isQuizCompleted) { + const quizData = await getUserQuiz(user.userId!); + setUserQuiz(quizData); + } else { + const qs = await getQuestions(); + setQuestions(qs); + } + } catch (err: any) { + toast.error('خطا در بارگذاری اطلاعات'); + } finally { + setQuizLoading(false); + setKhatmLoading(false); + } + }; + + const handleStartQuiz = () => { + setShowQuizDialog(true); + }; + + const handleSubmitQuiz = async () => { + const answeredCount = Object.keys(answers).length; + if (answeredCount < questions.length) { + toast.error(`لطفاً به تمام ${questions.length} سوال پاسخ دهید.`); + return; + } + + setSubmittingQuiz(true); + try { + const result = await submitQuiz( + user.userId!, + Object.entries(answers).map(([questionId, selectedOption]) => ({ + questionId: Number(questionId), + selectedOption, + })) + ); + + setQuizResult({ + participationPoints: result.participationPoints, + correctCount: result.correctCount, + totalQuizPoints: result.totalQuizPoints, + }); + setTotalScore(result.totalScore); + setUser({ isQuizCompleted: true, totalScore: result.totalScore }); + + // Load the completed quiz + const quizData = await getUserQuiz(user.userId!); + setUserQuiz(quizData); + } catch (err: any) { + toast.error(err.response?.data?.message || 'خطا در ثبت پاسخ‌ها'); + } finally { + setSubmittingQuiz(false); + } + }; + + const handleSaveKhatm = async () => { + setSavingKhatm(true); + try { + const selections = Object.entries(khatmSelections).map(([khatmItemId, isSelected]) => ({ + khatmItemId: Number(khatmItemId), + isSelected, + })); + + const result = await saveKhatmSelections(user.userId!, selections); + setTotalScore(result.totalScore); + setUser({ totalScore: result.totalScore, khatmSelections: Object.entries(khatmSelections).filter(([, v]) => v).map(([k]) => Number(k)) }); + toast.success('تغییرات با موفقیت ذخیره شد.'); + } catch (err: any) { + toast.error('خطا در ذخیره تغییرات'); + } finally { + setSavingKhatm(false); + } + }; + + const handleLogout = () => { + resetUser(); + navigate('/'); + }; + + if (!user.userId) return null; + + return ( +
+ {/* Header */} +
+
+
+ + داشبورد +
+
+
+ + {totalScore} + امتیاز +
+ +
+
+
+ + {/* Section Navigation */} + + +
+ {/* Welcome */} +
+

+ {user.fullName} خوش آمدی، +

+

امتیاز کل شما: {totalScore}

+
+ +
+ {/* Quiz Card */} +
+ + +
+
+ +
+
+ مسابقه + + {user.isQuizCompleted + ? 'شما قبلاً در مسابقه شرکت کرده‌اید' + : 'به سوالات چهارگزینه‌ای پاسخ دهید'} + +
+
+
+ + {quizLoading ? ( +
+ ) : user.isQuizCompleted && userQuiz ? ( +
+
+
+

امتیاز شرکت

+

{userQuiz.participationPoints}

+
+
+

پاسخ صحیح

+

{userQuiz.correctCount}

+
+
+

امتیاز کل

+

{userQuiz.totalQuizPoints}

+
+
+ +
+ {userQuiz.questions.map((q, index) => ( +
+

+ {index + 1}. {q.questionText} +

+
+ {q.options.map((opt, optIndex) => ( +
+ {opt} +
+ {q.selectedOption === optIndex + 1 && ( +
+ )} +
+
+ ))} +
+
+ ))} +
+
+ ) : ( +
+ +

+ آماده شرکت در مسابقه هستید؟ {questions.length} سوال چهارگزینه‌ای منتظر شماست. +

+ +
+ )} + + + +
+ + {/* Khatm Card */} +
+ + +
+
+ +
+
+ ثبت ختم‌ها + + ختم‌های مورد نظر خود را انتخاب کنید + +
+
+
+ + {khatmLoading ? ( +
+ {[1, 2, 3].map(i => ( +
+ ))} +
+ ) : ( +
+ {khatmItems.map(item => ( +
+
+ { + setKhatmSelections(prev => ({ + ...prev, + [item.id]: checked === true, + })); + }} + /> + + {item.url && ( + + + + )} +
+ + {item.points} امتیاز + +
+ ))} + +
+ )} + + +
+
+
+ + {/* Quiz Dialog */} + { + if (!open && !quizResult) setShowQuizDialog(false); + }}> + + {quizResult ? ( + <> + + نتیجه مسابقه + + پاسخ‌های شما با موفقیت ثبت شد + + +
+
+

امتیاز شرکت

+

{quizResult.participationPoints}

+
+
+

پاسخ‌های صحیح

+

{quizResult.correctCount}

+
+
+

امتیاز مسابقه

+

{quizResult.totalQuizPoints}

+
+
+ + + ) : ( + <> + + سوالات مسابقه + + به تمام سوالات پاسخ دهید و سپس دکمه ثبت را بزنید + + +
+ {questions.map((q, index) => ( +
+

+ {index + 1}. {q.questionText} +

+ { + setAnswers(prev => ({ ...prev, [q.id]: Number(value) })); + }} + > + {q.options.map((opt, optIndex) => ( +
+ + +
+ ))} +
+
+ ))} +
+ + + )} +
+
+
+ ); +} diff --git a/backend/ClientApp/src/pages/LandingPage.tsx b/backend/ClientApp/src/pages/LandingPage.tsx new file mode 100644 index 0000000..cfb3917 --- /dev/null +++ b/backend/ClientApp/src/pages/LandingPage.tsx @@ -0,0 +1,298 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Copy, Check, UserPlus, LogIn, ExternalLink, Award, Gamepad2 } from 'lucide-react'; +import { toast } from 'sonner'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { getConfig, registerUser, loginUser, type SiteConfig } from '@/lib/api'; +import { useUser } from '@/context/UserContext'; + +export default function LandingPage() { + const navigate = useNavigate(); + const { setUser } = useUser(); + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [showAuthDialog, setShowAuthDialog] = useState(false); + + // Register form + const [regName, setRegName] = useState(''); + const [regContact, setRegContact] = useState(''); + const [regSubmitting, setRegSubmitting] = useState(false); + + // Login form + const [loginCode, setLoginCode] = useState(''); + const [loginSubmitting, setLoginSubmitting] = useState(false); + + // Tracking code dialog + const [showTrackingDialog, setShowTrackingDialog] = useState(false); + const [trackingCode, setTrackingCode] = useState(''); + const [copied, setCopied] = useState(false); + + // Active tab + const [activeTab, setActiveTab] = useState('register'); + + useEffect(() => { + getConfig() + .then(setConfig) + .catch(() => toast.error('خطا در دریافت اطلاعات سایت')) + .finally(() => setLoading(false)); + }, []); + + const handleRegister = async (e: React.FormEvent) => { + e.preventDefault(); + if (!regName.trim() || !regContact.trim()) { + toast.error('لطفاً نام و شناسه خود را وارد کنید.'); + return; + } + if (regContact.trim().length < 5) { + toast.error('شناسه باید حداقل ۵ کاراکتر باشد.'); + return; + } + + setRegSubmitting(true); + try { + const result = await registerUser(regName.trim(), regContact.trim()); + setTrackingCode(result.trackingCode); + setShowTrackingDialog(true); + setUser({ userId: result.userId, fullName: regName.trim(), contact: regContact.trim() }); + } catch (err: any) { + if (err.response?.status === 409) { + toast.error('این شناسه قبلاً ثبت شده است. لطفاً با کد رهگیری وارد شوید.'); + setActiveTab('login'); + } else { + toast.error(err.response?.data?.message || 'خطا در ثبت‌نام'); + } + } finally { + setRegSubmitting(false); + } + }; + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + if (!loginCode.trim()) { + toast.error('لطفاً کد رهگیری را وارد کنید.'); + return; + } + + setLoginSubmitting(true); + try { + const result = await loginUser(loginCode.trim()); + setUser({ + userId: result.userId, + fullName: result.fullName, + contact: result.contact, + isQuizCompleted: result.isQuizCompleted, + khatmSelections: result.khatmSelections, + }); + navigate('/dashboard'); + } catch (err: any) { + if (err.response?.status === 404) { + toast.error('کد رهگیری نامعتبر است.'); + } else { + toast.error('خطا در ورود'); + } + } finally { + setLoginSubmitting(false); + } + }; + + const copyTrackingCode = () => { + navigator.clipboard.writeText(trackingCode); + setCopied(true); + toast.success('کد رهگیری کپی شد.'); + setTimeout(() => setCopied(false), 2000); + }; + + const goToDashboard = () => { + setShowTrackingDialog(false); + navigate('/dashboard'); + }; + + if (loading) { + return ( +
+
در حال بارگذاری...
+
+ ); + } + + return ( +
+ {/* Hero Section */} +
+
+
+ {/* Decorative top border */} +
+
+
+ + {/* Title */} +

+ {config?.siteTitle || 'مسابقه بزرگ غدیر'} +

+ + {/* Subtitle */} +

+ {config?.subtitle} +

+ + {/* Channel Links */} + {config?.channels && ( +
+ {config.channels.map((channel, index) => ( + + + {channel.name} + + ))} +
+ )} + + {/* Short Text */} + {config?.shortText && ( +

+ {config.shortText} +

+ )} + + {/* Decorative divider */} +
+
+ +
+
+ + {/* CTA Button - Enter Contest */} +
+ +
+
+
+ + {/* Auth Dialog */} + + + + ورود به مسابقه + + برای شرکت در مسابقه، ثبت‌نام کنید یا با کد رهگیری وارد شوید + + + + + + + ثبت‌نام جدید + + + + ورود با کد رهگیری + + + + +
+
+ + setRegName(e.target.value)} + required + className="text-center" + dir="rtl" + /> +
+
+ + setRegContact(e.target.value)} + required + minLength={5} + className="text-center" + dir="ltr" + /> +
+ +
+
+ + +
+
+ + setLoginCode(e.target.value)} + required + maxLength={4} + className="text-center font-mono text-lg tracking-widest" + dir="ltr" + /> +
+ +
+
+
+
+
+ + {/* Tracking Code Dialog */} + + + + ثبت‌نام با موفقیت انجام شد! + + کد رهگیری خود را حتماً یادداشت کنید. این تنها راه دسترسی مجدد به حساب شماست. + + +
+
+

کد رهگیری شما:

+

+ {trackingCode} +

+
+ + +
+
+
+
+ ); +} diff --git a/backend/ClientApp/tsconfig.app.json b/backend/ClientApp/tsconfig.app.json new file mode 100644 index 0000000..a65e501 --- /dev/null +++ b/backend/ClientApp/tsconfig.app.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/backend/ClientApp/tsconfig.json b/backend/ClientApp/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/backend/ClientApp/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/backend/ClientApp/tsconfig.node.json b/backend/ClientApp/tsconfig.node.json new file mode 100644 index 0000000..d3c52ea --- /dev/null +++ b/backend/ClientApp/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/backend/ClientApp/vite.config.ts b/backend/ClientApp/vite.config.ts new file mode 100644 index 0000000..1bc6314 --- /dev/null +++ b/backend/ClientApp/vite.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + server: { + port: 5173, + strictPort: true, + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + }, + }, + }, + build: { + outDir: 'dist', + emptyOutDir: true, + }, +}) diff --git a/backend/Controllers/AdminController.cs b/backend/Controllers/AdminController.cs new file mode 100644 index 0000000..7469c32 --- /dev/null +++ b/backend/Controllers/AdminController.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using backend.Data; +using backend.DTOs; +using backend.Models; + +namespace backend.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AdminController : ControllerBase +{ + private readonly AppDbContext _db; + private readonly AppConfig _appConfig; + + public AdminController(AppDbContext db, IOptions appConfig) + { + _db = db; + _appConfig = appConfig.Value; + } + + [HttpGet("report")] + public async Task GetReport([FromQuery] string password) + { + if (password != _appConfig.AdminPassword) + { + return StatusCode(403, new { message = "رمز عبور اشتباه است." }); + } + + var users = await _db.Users + .Include(u => u.QuizAttempt) + .Include(u => u.KhatmSelections) + .ThenInclude(ks => ks.KhatmItem) + .Include(u => u.TotalScore) + .OrderBy(u => u.Id) + .ToListAsync(); + + var report = users.Select(u => new AdminReportItem + { + UserId = u.Id, + FullName = u.FullName, + Contact = u.Contact, + TrackingCode = u.TrackingCode, + HasTakenQuiz = u.QuizAttempt != null, + ParticipationPoints = u.QuizAttempt?.ParticipationPoints ?? 0, + CorrectCount = u.QuizAttempt?.CorrectAnswersCount ?? 0, + TotalQuizPoints = u.QuizAttempt?.TotalQuizPoints ?? 0, + KhatmTitles = string.Join("، ", + u.KhatmSelections + .Where(ks => ks.IsSelected) + .Select(ks => ks.KhatmItem.Title)), + TotalScore = u.TotalScore?.TotalPoints ?? 0 + }).ToList(); + + return Ok(report); + } +} diff --git a/backend/Controllers/ConfigController.cs b/backend/Controllers/ConfigController.cs new file mode 100644 index 0000000..afaae89 --- /dev/null +++ b/backend/Controllers/ConfigController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using backend.Models; + +namespace backend.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ConfigController : ControllerBase +{ + private readonly AppConfig _appConfig; + + public ConfigController(IOptions appConfig) + { + _appConfig = appConfig.Value; + } + + [HttpGet] + public IActionResult GetConfig() + { + return Ok(new + { + siteTitle = _appConfig.SiteTitle, + subtitle = _appConfig.Subtitle, + channels = _appConfig.Channels.Select(c => new { name = c.Name, url = c.Url }), + shortText = _appConfig.ShortText, + quizParticipationPoints = _appConfig.Quiz.ParticipationPoints, + quizCorrectAnswerPoints = _appConfig.Quiz.CorrectAnswerPoints + }); + } +} diff --git a/backend/Controllers/KhatmController.cs b/backend/Controllers/KhatmController.cs new file mode 100644 index 0000000..d7c40b4 --- /dev/null +++ b/backend/Controllers/KhatmController.cs @@ -0,0 +1,105 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using backend.Data; +using backend.DTOs; +using backend.Models; + +namespace backend.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class KhatmController : ControllerBase +{ + private readonly AppDbContext _db; + + public KhatmController(AppDbContext db) + { + _db = db; + } + + [HttpGet("items")] + public async Task GetItems() + { + var items = await _db.KhatmItems + .OrderBy(k => k.Id) + .Select(k => new KhatmItemDto + { + Id = k.Id, + Title = k.Title, + Url = k.Url, + Points = k.Points + }) + .ToListAsync(); + + return Ok(items); + } + + [HttpPost("save")] + public async Task SaveSelections([FromBody] KhatmSaveRequest request) + { + var user = await _db.Users.FindAsync(request.UserId); + if (user == null) + { + return NotFound(new { message = "کاربر یافت نشد." }); + } + + foreach (var selection in request.Selections) + { + var existing = await _db.UserKhatmSelections + .FirstOrDefaultAsync(ks => ks.UserId == request.UserId && ks.KhatmItemId == selection.KhatmItemId); + + if (existing != null) + { + existing.IsSelected = selection.IsSelected; + existing.LastModified = DateTime.UtcNow; + } + else + { + _db.UserKhatmSelections.Add(new UserKhatmSelection + { + UserId = request.UserId, + KhatmItemId = selection.KhatmItemId, + IsSelected = selection.IsSelected, + LastModified = DateTime.UtcNow + }); + } + } + + // Recalculate total score + await RecalculateTotalScore(request.UserId); + await _db.SaveChangesAsync(); + + var totalScore = await _db.UserTotalScores + .Where(ts => ts.UserId == request.UserId) + .Select(ts => ts.TotalPoints) + .FirstOrDefaultAsync(); + + return Ok(new KhatmSaveResponse + { + Success = true, + TotalScore = totalScore + }); + } + + private async Task RecalculateTotalScore(int userId) + { + var quizPoints = await _db.UserQuizAttempts + .Where(qa => qa.UserId == userId) + .Select(qa => (int?)qa.TotalQuizPoints) + .FirstOrDefaultAsync() ?? 0; + + var khatmPoints = await _db.UserKhatmSelections + .Where(ks => ks.UserId == userId && ks.IsSelected) + .SumAsync(ks => ks.KhatmItem.Points); + + int total = quizPoints + khatmPoints; + + var totalScore = await _db.UserTotalScores + .FirstOrDefaultAsync(ts => ts.UserId == userId); + + if (totalScore != null) + { + totalScore.TotalPoints = total; + } + } +} diff --git a/backend/Controllers/QuizController.cs b/backend/Controllers/QuizController.cs new file mode 100644 index 0000000..7ccbbe6 --- /dev/null +++ b/backend/Controllers/QuizController.cs @@ -0,0 +1,165 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using backend.Data; +using backend.DTOs; +using backend.Models; + +namespace backend.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class QuizController : ControllerBase +{ + private readonly AppDbContext _db; + private readonly AppConfig _appConfig; + + public QuizController(AppDbContext db, IOptions appConfig) + { + _db = db; + _appConfig = appConfig.Value; + } + + [HttpGet("questions")] + public async Task GetQuestions() + { + var questions = await _db.QuizQuestions + .OrderBy(q => q.Id) + .Select(q => new QuestionDto + { + Id = q.Id, + QuestionText = q.QuestionText, + Options = new List { q.Option1, q.Option2, q.Option3, q.Option4 } + }) + .ToListAsync(); + + return Ok(questions); + } + + [HttpPost("submit")] + public async Task SubmitQuiz([FromBody] QuizSubmitRequest request) + { + // Check if user already attempted + var existingAttempt = await _db.UserQuizAttempts + .AnyAsync(qa => qa.UserId == request.UserId); + + if (existingAttempt) + { + return BadRequest(new { message = "شما قبلاً در مسابقه شرکت کرده‌اید." }); + } + + var user = await _db.Users.FindAsync(request.UserId); + if (user == null) + { + return NotFound(new { message = "کاربر یافت نشد." }); + } + + var questions = await _db.QuizQuestions.ToListAsync(); + var participationPoints = _appConfig.Quiz.ParticipationPoints; + var correctAnswerPoints = _appConfig.Quiz.CorrectAnswerPoints; + + int correctCount = 0; + var answers = new List(); + + foreach (var answer in request.Answers) + { + var question = questions.FirstOrDefault(q => q.Id == answer.QuestionId); + if (question == null) continue; + + bool isCorrect = question.CorrectOption == answer.SelectedOption; + if (isCorrect) correctCount++; + + answers.Add(new UserQuizAnswer + { + QuestionId = answer.QuestionId, + SelectedOption = answer.SelectedOption, + IsCorrect = isCorrect + }); + } + + int totalQuizPoints = participationPoints + (correctCount * correctAnswerPoints); + + var attempt = new UserQuizAttempt + { + UserId = request.UserId, + ParticipationPoints = participationPoints, + CorrectAnswersCount = correctCount, + TotalQuizPoints = totalQuizPoints, + CompletedAt = DateTime.UtcNow, + Answers = answers + }; + + _db.UserQuizAttempts.Add(attempt); + + // Update total score + await RecalculateTotalScore(request.UserId); + + await _db.SaveChangesAsync(); + + var totalScore = await _db.UserTotalScores + .Where(ts => ts.UserId == request.UserId) + .Select(ts => ts.TotalPoints) + .FirstOrDefaultAsync(); + + return Ok(new QuizSubmitResponse + { + ParticipationPoints = participationPoints, + CorrectCount = correctCount, + TotalQuizPoints = totalQuizPoints, + TotalScore = totalScore + }); + } + + [HttpGet("user/{userId}")] + public async Task GetUserQuiz(int userId) + { + var attempt = await _db.UserQuizAttempts + .Include(qa => qa.Answers) + .ThenInclude(a => a.Question) + .FirstOrDefaultAsync(qa => qa.UserId == userId); + + if (attempt == null) + { + return NotFound(new { message = "کاربر هنوز در مسابقه شرکت نکرده است." }); + } + + var questions = attempt.Answers.Select(a => new UserQuestionDto + { + Id = a.Question.Id, + QuestionText = a.Question.QuestionText, + Options = new List { a.Question.Option1, a.Question.Option2, a.Question.Option3, a.Question.Option4 }, + SelectedOption = a.SelectedOption, + IsCorrect = a.IsCorrect + }).ToList(); + + return Ok(new UserQuizResponse + { + Questions = questions, + ParticipationPoints = attempt.ParticipationPoints, + CorrectCount = attempt.CorrectAnswersCount, + TotalQuizPoints = attempt.TotalQuizPoints + }); + } + + private async Task RecalculateTotalScore(int userId) + { + var quizPoints = await _db.UserQuizAttempts + .Where(qa => qa.UserId == userId) + .Select(qa => (int?)qa.TotalQuizPoints) + .FirstOrDefaultAsync() ?? 0; + + var khatmPoints = await _db.UserKhatmSelections + .Where(ks => ks.UserId == userId && ks.IsSelected) + .SumAsync(ks => ks.KhatmItem.Points); + + int total = quizPoints + khatmPoints; + + var totalScore = await _db.UserTotalScores + .FirstOrDefaultAsync(ts => ts.UserId == userId); + + if (totalScore != null) + { + totalScore.TotalPoints = total; + } + } +} diff --git a/backend/Controllers/UserController.cs b/backend/Controllers/UserController.cs new file mode 100644 index 0000000..2d13eee --- /dev/null +++ b/backend/Controllers/UserController.cs @@ -0,0 +1,129 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using backend.Data; +using backend.DTOs; +using backend.Models; + +namespace backend.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class UserController : ControllerBase +{ + private readonly AppDbContext _db; + + public UserController(AppDbContext db) + { + _db = db; + } + + [HttpPost("register")] + public async Task Register([FromBody] RegisterRequest request) + { + if (string.IsNullOrWhiteSpace(request.FullName) || string.IsNullOrWhiteSpace(request.Contact)) + { + return BadRequest(new { message = "نام و شناسه الزامی هستند." }); + } + + if (request.Contact.Length < 5) + { + return BadRequest(new { message = "شناسه باید حداقل ۵ کاراکتر باشد." }); + } + + // Check if contact already exists + var existingUser = await _db.Users.FirstOrDefaultAsync(u => u.Contact == request.Contact); + if (existingUser != null) + { + return Conflict(new { message = "این شناسه قبلاً ثبت شده است. لطفاً با کد رهگیری وارد شوید." }); + } + + // Generate unique tracking code + string trackingCode; + do + { + trackingCode = GenerateTrackingCode(); + } while (await _db.Users.AnyAsync(u => u.TrackingCode == trackingCode)); + + var user = new User + { + FullName = request.FullName.Trim(), + Contact = request.Contact.Trim(), + TrackingCode = trackingCode + }; + + _db.Users.Add(user); + + // Save the user first so that user.Id is populated by the database + await _db.SaveChangesAsync(); + + // Create initial total score record with the now-populated user.Id + var totalScore = new UserTotalScore + { + UserId = user.Id, + TotalPoints = 0 + }; + _db.UserTotalScores.Add(totalScore); + + await _db.SaveChangesAsync(); + + return Ok(new RegisterResponse + { + UserId = user.Id, + TrackingCode = trackingCode + }); + } + + [HttpPost("login")] + public async Task Login([FromBody] LoginRequest request) + { + if (string.IsNullOrWhiteSpace(request.TrackingCode)) + { + return BadRequest(new { message = "کد رهگیری الزامی است." }); + } + + var user = await _db.Users + .Include(u => u.QuizAttempt) + .Include(u => u.KhatmSelections) + .FirstOrDefaultAsync(u => u.TrackingCode == request.TrackingCode.Trim()); + + if (user == null) + { + return NotFound(new { message = "کد رهگیری نامعتبر است." }); + } + + return Ok(new LoginResponse + { + UserId = user.Id, + FullName = user.FullName, + Contact = user.Contact, + IsQuizCompleted = user.QuizAttempt != null, + KhatmSelections = user.KhatmSelections + .Where(ks => ks.IsSelected) + .Select(ks => ks.KhatmItemId) + .ToList() + }); + } + + [HttpGet("{userId}/score")] + public async Task GetScore(int userId) + { + var totalScore = await _db.UserTotalScores + .FirstOrDefaultAsync(ts => ts.UserId == userId); + + if (totalScore == null) + { + return NotFound(new { message = "کاربر یافت نشد." }); + } + + return Ok(new UserScoreResponse + { + TotalScore = totalScore.TotalPoints + }); + } + + private static string GenerateTrackingCode() + { + var random = new Random(); + return random.Next(1000, 10000).ToString(); + } +} diff --git a/backend/DTOs/AdminDtos.cs b/backend/DTOs/AdminDtos.cs new file mode 100644 index 0000000..58e6dc3 --- /dev/null +++ b/backend/DTOs/AdminDtos.cs @@ -0,0 +1,15 @@ +namespace backend.DTOs; + +public class AdminReportItem +{ + public int UserId { get; set; } + public string FullName { get; set; } = string.Empty; + public string Contact { get; set; } = string.Empty; + public string TrackingCode { get; set; } = string.Empty; + public bool HasTakenQuiz { get; set; } + public int ParticipationPoints { get; set; } + public int CorrectCount { get; set; } + public int TotalQuizPoints { get; set; } + public string KhatmTitles { get; set; } = string.Empty; + public int TotalScore { get; set; } +} diff --git a/backend/DTOs/KhatmDtos.cs b/backend/DTOs/KhatmDtos.cs new file mode 100644 index 0000000..ab57730 --- /dev/null +++ b/backend/DTOs/KhatmDtos.cs @@ -0,0 +1,27 @@ +namespace backend.DTOs; + +public class KhatmItemDto +{ + public int Id { get; set; } + public string Title { get; set; } = string.Empty; + public string? Url { get; set; } + public int Points { get; set; } +} + +public class KhatmSaveRequest +{ + public int UserId { get; set; } + public List Selections { get; set; } = new(); +} + +public class KhatmSelectionDto +{ + public int KhatmItemId { get; set; } + public bool IsSelected { get; set; } +} + +public class KhatmSaveResponse +{ + public bool Success { get; set; } + public int TotalScore { get; set; } +} diff --git a/backend/DTOs/QuizDtos.cs b/backend/DTOs/QuizDtos.cs new file mode 100644 index 0000000..d93b7e7 --- /dev/null +++ b/backend/DTOs/QuizDtos.cs @@ -0,0 +1,45 @@ +namespace backend.DTOs; + +public class QuestionDto +{ + public int Id { get; set; } + public string QuestionText { get; set; } = string.Empty; + public List Options { get; set; } = new(); +} + +public class QuizSubmitRequest +{ + public int UserId { get; set; } + public List Answers { get; set; } = new(); +} + +public class AnswerDto +{ + public int QuestionId { get; set; } + public int SelectedOption { get; set; } +} + +public class QuizSubmitResponse +{ + public int ParticipationPoints { get; set; } + public int CorrectCount { get; set; } + public int TotalQuizPoints { get; set; } + public int TotalScore { get; set; } +} + +public class UserQuizResponse +{ + public List Questions { get; set; } = new(); + public int ParticipationPoints { get; set; } + public int CorrectCount { get; set; } + public int TotalQuizPoints { get; set; } +} + +public class UserQuestionDto +{ + public int Id { get; set; } + public string QuestionText { get; set; } = string.Empty; + public List Options { get; set; } = new(); + public int SelectedOption { get; set; } + public bool IsCorrect { get; set; } +} diff --git a/backend/DTOs/UserDtos.cs b/backend/DTOs/UserDtos.cs new file mode 100644 index 0000000..434c4be --- /dev/null +++ b/backend/DTOs/UserDtos.cs @@ -0,0 +1,32 @@ +namespace backend.DTOs; + +public class RegisterRequest +{ + public string FullName { get; set; } = string.Empty; + public string Contact { get; set; } = string.Empty; +} + +public class RegisterResponse +{ + public int UserId { get; set; } + public string TrackingCode { get; set; } = string.Empty; +} + +public class LoginRequest +{ + public string TrackingCode { get; set; } = string.Empty; +} + +public class LoginResponse +{ + public int UserId { get; set; } + public string FullName { get; set; } = string.Empty; + public string Contact { get; set; } = string.Empty; + public bool IsQuizCompleted { get; set; } + public List KhatmSelections { get; set; } = new(); +} + +public class UserScoreResponse +{ + public int TotalScore { get; set; } +} diff --git a/backend/Data/AppDbContext.cs b/backend/Data/AppDbContext.cs new file mode 100644 index 0000000..5f3c123 --- /dev/null +++ b/backend/Data/AppDbContext.cs @@ -0,0 +1,192 @@ +using Microsoft.EntityFrameworkCore; +using backend.Models; + +namespace backend.Data; + +public class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions options) : base(options) { } + + public DbSet Users => Set(); + public DbSet QuizQuestions => Set(); + public DbSet UserQuizAttempts => Set(); + public DbSet UserQuizAnswers => Set(); + public DbSet KhatmItems => Set(); + public DbSet UserKhatmSelections => Set(); + public DbSet UserTotalScores => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // User + modelBuilder.Entity(entity => + { + entity.HasIndex(u => u.Contact).IsUnique(); + entity.HasIndex(u => u.TrackingCode).IsUnique(); + + entity.HasOne(u => u.QuizAttempt) + .WithOne(qa => qa.User) + .HasForeignKey(qa => qa.UserId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(u => u.TotalScore) + .WithOne(ts => ts.User) + .HasForeignKey(ts => ts.UserId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // UserQuizAttempt + modelBuilder.Entity(entity => + { + entity.HasIndex(qa => qa.UserId).IsUnique(); + + entity.HasMany(qa => qa.Answers) + .WithOne(a => a.QuizAttempt) + .HasForeignKey(a => a.UserQuizAttemptId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // UserQuizAnswer + modelBuilder.Entity(entity => + { + entity.HasOne(a => a.Question) + .WithMany(q => q.Answers) + .HasForeignKey(a => a.QuestionId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // UserKhatmSelection + modelBuilder.Entity(entity => + { + entity.HasIndex(uk => new { uk.UserId, uk.KhatmItemId }).IsUnique(); + + entity.HasOne(uk => uk.User) + .WithMany(u => u.KhatmSelections) + .HasForeignKey(uk => uk.UserId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(uk => uk.KhatmItem) + .WithMany(ki => ki.UserSelections) + .HasForeignKey(uk => uk.KhatmItemId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // UserTotalScore + modelBuilder.Entity(entity => + { + entity.HasIndex(ts => ts.UserId).IsUnique(); + }); + + // Seed data: KhatmItems + modelBuilder.Entity().HasData( + new KhatmItem { Id = 1, Title = "ختم نادعلی", Points = 5, Url = "https://example.com/khatm/nadali" }, + new KhatmItem { Id = 2, Title = "خطبه غدیر", Points = 10, Url = "https://example.com/khatm/ghadir" }, + new KhatmItem { Id = 3, Title = "دعای توسل", Points = 7, Url = "https://example.com/khatm/tavassol" } + ); + + // Seed data: QuizQuestions + modelBuilder.Entity().HasData( + new QuizQuestion + { + Id = 1, + QuestionText = "كدام يك از آيات زير مربوط به حجاب نمي باشد؟", + Option1 = "آيه 31 سوره نور", + Option2 = "آيه 58 – 60 سوره نور", + Option3 = "آيه 59 – 60 سوره احزاب", + Option4 = "آيه 59 – 60 سوره نساء", + CorrectOption = 4 + }, + new QuizQuestion + { + Id = 2, + QuestionText = "اين قسمت از آيه 31 سوره نور « قل للمومنات يغضضن من ابصار هن و يحفظن فروجهن» بيانگر كدام دو تكليف است؟", + Option1 = "پوشش – عفاف", + Option2 = "ترك نظر – پوشش", + Option3 = "ترك نظر – پاكدامني", + Option4 = "پوشش – پاكدامني", + CorrectOption = 3 + }, + new QuizQuestion + { + Id = 3, + QuestionText = "منظور از (( قواعد )) در سوره نور آيات 58-60 چه كساني هستند؟", + Option1 = "زنان سالخورده اي كه اميدي به ازدواج ايشان نمي باشد", + Option2 = "بردگان و مملوكان", + Option3 = "طفيلياني كه نيازي به زن ندارند", + Option4 = "اطفال نابالغ ولو مميز نزديك به بلوغ", + CorrectOption = 1 + }, + new QuizQuestion + { + Id = 4, + QuestionText = "هشتمین بخش خطبه غدیر در چه موضوعی است؟", + Option1 = "در مورد ثمرات ولایت و محبت اهل‌بیت", + Option2 = "در مورد جنبه غضب الهی", + Option3 = "در مورد امام مهدی علیه‌السلام", + Option4 = "اقرار لسانی", + CorrectOption = 3 + }, + new QuizQuestion + { + Id = 5, + QuestionText = "لقب «امیرالمؤمنین» توسط چه کسی به مولا علی علیه‌السلام اطلاق شد؟", + Option1 = "این لقب به دستور مستقیم از جانب خداوند به ایشان داده شد", + Option2 = "پیامبر اکرم، خودشان این لقب را انتخاب کردند", + Option3 = "مردم به علت فضائل بسیار ایشان، این لقب را انتخاب کردند", + Option4 = "به درخواست خود ایشان بود", + CorrectOption = 1 + }, + new QuizQuestion + { + Id = 6, + QuestionText = "طبق فرمایش پیامبر اکرم در غدیر، عبارت «إِنَّ الإِنسَانَ لَفِي خُسْرٍ»، «انسان در زیان است» درباره چه کسانی است؟", + Option1 = "کسانی که خبر اسلام به آنان نرسیده است", + Option2 = "کسانی که در مدیریت زمان ضعیف هستند", + Option3 = "دشمنان آل محمد (صلی‌الله‌علیه‌وآله)", + Option4 = "اهل کتاب", + CorrectOption = 3 + }, + new QuizQuestion + { + Id = 7, + QuestionText = "در کدام آیه از قرآن مجید خداوند می فرماید که درباره علی تفریط و کوتاهی نکنید؟", + Option1 = "سوره کهف آیه ۲۸", + Option2 = "سوره انعام آیه ۶۱", + Option3 = "سوره زمر ۵۶", + Option4 = "سوره طه ۴۵", + CorrectOption = 3 + }, + new QuizQuestion + { + Id = 8, + QuestionText = "طبق آیه 67 سوره مائده وظيفه رسول خدا در برابر امامت چيست و آيه در شأن چه كسی نازل شده است؟\n«يَا أَيُّهَا الرَّسُولُ بَلِّغْ مَا أُنْزِلَ إِلَيْكَ مِنْ رَبِّكَ وَإِنْ لَمْ تَفْعَلْ فَمَا بَلَّغْتَ رِسَالَتَهُ ...»", + Option1 = "تعيين امامت ،علی عليه السلام", + Option2 = "تبليغ امامت، علی عليه السلام", + Option3 = "تعيين و تبليغ امامت، علی عليه السلام", + Option4 = "سپردن تعیین امامت به مردم", + CorrectOption = 2 + }, + new QuizQuestion + { + Id = 9, + QuestionText = "طبق روایت امام رضا علیه السلام برترین عید امت اسلامی کدام است؟", + Option1 = "عید غدیر، روز تبلیغ و معرفی امیرالمومنین به عنوان جانشین پیامبر", + Option2 = "روز جمعه", + Option3 = "عید قربان، حج تمتع", + Option4 = "عید فطر، پایان یک ماه بندگی", + CorrectOption = 1 + }, + new QuizQuestion + { + Id = 10, + QuestionText = "با توجه به مطلبی که در آیه شریفه «الْيَوْمَ أَكْمَلْتُ لَكُمْ دِينَكُمْ وَ أَتْمَمْتُ عَلَيْكُمْ نِعْمَتِي وَ رَضِيتُ لَكُمُ الْإِسْلامَ دِينا» در روز غدیر نازل گشته است. کدام گزینه صحیح نمی‌باشد؟", + Option1 = "با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) توسط پیامبر اکرم(صلی الله علیه و آله) دین کامل گشت", + Option2 = "خداوند با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) و یازده فرزند پاکشان از اسلام اعلام رضایت فرمودند", + Option3 = "شرط اینکه دین شخص مسلمان مورد رضایت خداوند متعال قرار گیرد پذیرش ولایت و امامت ائمه (علیهم السلام) است", + Option4 = "امامت از فروع دین بوده و قبول یا عدم پذیرش آن لطمه‌ای به آن شخص وارد نمی‌کند", + CorrectOption = 4 + } + ); + } +} diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..be7de59 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,25 @@ +# Stage 1: Build the frontend +FROM node:20-alpine AS frontend-build +WORKDIR /app +COPY ClientApp/package*.json ./ +RUN npm ci +COPY ClientApp/ . +RUN npm run build + +# Stage 2: Build the backend +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS backend-build +WORKDIR /src +COPY ["backend.csproj", "."] +RUN dotnet restore +COPY . . +# Copy the frontend build output from the previous stage +COPY --from=frontend-build /app/dist ./ClientApp/dist +RUN dotnet publish -c Release -o /app/publish + +# Stage 3: Runtime +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime +WORKDIR /app +EXPOSE 8080 +ENV ASPNETCORE_URLS=http://+:8080 +COPY --from=backend-build /app/publish . +ENTRYPOINT ["dotnet", "backend.dll"] diff --git a/backend/Migrations/20260603125735_InitialCreate.Designer.cs b/backend/Migrations/20260603125735_InitialCreate.Designer.cs new file mode 100644 index 0000000..f626618 --- /dev/null +++ b/backend/Migrations/20260603125735_InitialCreate.Designer.cs @@ -0,0 +1,374 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using backend.Data; + +#nullable disable + +namespace backend.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260603125735_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("KhatmItems"); + + b.HasData( + new + { + Id = 1, + Points = 5, + Title = "ختم نادعلی" + }, + new + { + Id = 2, + Points = 10, + Title = "خطبه غدیر" + }, + new + { + Id = 3, + Points = 7, + Title = "دعای توسل" + }); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CorrectOption") + .HasColumnType("int"); + + b.Property("Option1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option2") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option3") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option4") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("QuestionText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("QuizQuestions"); + + b.HasData( + new + { + Id = 1, + CorrectOption = 1, + Option1 = "سال ۱۰ هجری", + Option2 = "سال ۱۱ هجری", + Option3 = "سال ۹ هجری", + Option4 = "سال ۱۲ هجری", + QuestionText = "غدیر در چه سالی واقع شد؟" + }, + new + { + Id = 2, + CorrectOption = 3, + Option1 = "ابوبکر", + Option2 = "عمر", + Option3 = "علی (ع)", + Option4 = "عثمان", + QuestionText = "پیامبر اکرم (ص) در غدیر چه کسی را به عنوان جانشین معرفی کردند؟" + }, + new + { + Id = 3, + CorrectOption = 2, + Option1 = "مسجد النبی", + Option2 = "غدیر خم", + Option3 = "مکه", + Option4 = "مدینه", + QuestionText = "خطبه غدیر در کجا ایراد شد؟" + }); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TrackingCode") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.HasKey("Id"); + + b.HasIndex("Contact") + .IsUnique(); + + b.HasIndex("TrackingCode") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsSelected") + .HasColumnType("bit"); + + b.Property("KhatmItemId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("KhatmItemId"); + + b.HasIndex("UserId", "KhatmItemId") + .IsUnique(); + + b.ToTable("UserKhatmSelections"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsCorrect") + .HasColumnType("bit"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("SelectedOption") + .HasColumnType("int"); + + b.Property("UserQuizAttemptId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("UserQuizAttemptId"); + + b.ToTable("UserQuizAnswers"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CorrectAnswersCount") + .HasColumnType("int"); + + b.Property("ParticipationPoints") + .HasColumnType("int"); + + b.Property("TotalQuizPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserQuizAttempts"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("TotalPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserTotalScores"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.HasOne("backend.Models.KhatmItem", "KhatmItem") + .WithMany("UserSelections") + .HasForeignKey("KhatmItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.User", "User") + .WithMany("KhatmSelections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KhatmItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.HasOne("backend.Models.QuizQuestion", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.UserQuizAttempt", "QuizAttempt") + .WithMany("Answers") + .HasForeignKey("UserQuizAttemptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + + b.Navigation("QuizAttempt"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("QuizAttempt") + .HasForeignKey("backend.Models.UserQuizAttempt", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("TotalScore") + .HasForeignKey("backend.Models.UserTotalScore", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Navigation("UserSelections"); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Navigation("KhatmSelections"); + + b.Navigation("QuizAttempt"); + + b.Navigation("TotalScore"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Navigation("Answers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Migrations/20260603125735_InitialCreate.cs b/backend/Migrations/20260603125735_InitialCreate.cs new file mode 100644 index 0000000..f24dbe9 --- /dev/null +++ b/backend/Migrations/20260603125735_InitialCreate.cs @@ -0,0 +1,253 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace backend.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "KhatmItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Points = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_KhatmItems", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "QuizQuestions", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + QuestionText = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Option1 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Option2 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Option3 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Option4 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + CorrectOption = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_QuizQuestions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + FullName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Contact = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + TrackingCode = table.Column(type: "nvarchar(8)", maxLength: 8, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserKhatmSelections", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "int", nullable: false), + KhatmItemId = table.Column(type: "int", nullable: false), + IsSelected = table.Column(type: "bit", nullable: false), + LastModified = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserKhatmSelections", x => x.Id); + table.ForeignKey( + name: "FK_UserKhatmSelections_KhatmItems_KhatmItemId", + column: x => x.KhatmItemId, + principalTable: "KhatmItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserKhatmSelections_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserQuizAttempts", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "int", nullable: false), + ParticipationPoints = table.Column(type: "int", nullable: false), + CorrectAnswersCount = table.Column(type: "int", nullable: false), + TotalQuizPoints = table.Column(type: "int", nullable: false), + CompletedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserQuizAttempts", x => x.Id); + table.ForeignKey( + name: "FK_UserQuizAttempts_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserTotalScores", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "int", nullable: false), + TotalPoints = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserTotalScores", x => x.Id); + table.ForeignKey( + name: "FK_UserTotalScores_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserQuizAnswers", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserQuizAttemptId = table.Column(type: "int", nullable: false), + QuestionId = table.Column(type: "int", nullable: false), + SelectedOption = table.Column(type: "int", nullable: false), + IsCorrect = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserQuizAnswers", x => x.Id); + table.ForeignKey( + name: "FK_UserQuizAnswers_QuizQuestions_QuestionId", + column: x => x.QuestionId, + principalTable: "QuizQuestions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserQuizAnswers_UserQuizAttempts_UserQuizAttemptId", + column: x => x.UserQuizAttemptId, + principalTable: "UserQuizAttempts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "KhatmItems", + columns: new[] { "Id", "Points", "Title" }, + values: new object[,] + { + { 1, 5, "ختم نادعلی" }, + { 2, 10, "خطبه غدیر" }, + { 3, 7, "دعای توسل" } + }); + + migrationBuilder.InsertData( + table: "QuizQuestions", + columns: new[] { "Id", "CorrectOption", "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[,] + { + { 1, 1, "سال ۱۰ هجری", "سال ۱۱ هجری", "سال ۹ هجری", "سال ۱۲ هجری", "غدیر در چه سالی واقع شد؟" }, + { 2, 3, "ابوبکر", "عمر", "علی (ع)", "عثمان", "پیامبر اکرم (ص) در غدیر چه کسی را به عنوان جانشین معرفی کردند؟" }, + { 3, 2, "مسجد النبی", "غدیر خم", "مکه", "مدینه", "خطبه غدیر در کجا ایراد شد؟" } + }); + + migrationBuilder.CreateIndex( + name: "IX_UserKhatmSelections_KhatmItemId", + table: "UserKhatmSelections", + column: "KhatmItemId"); + + migrationBuilder.CreateIndex( + name: "IX_UserKhatmSelections_UserId_KhatmItemId", + table: "UserKhatmSelections", + columns: new[] { "UserId", "KhatmItemId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserQuizAnswers_QuestionId", + table: "UserQuizAnswers", + column: "QuestionId"); + + migrationBuilder.CreateIndex( + name: "IX_UserQuizAnswers_UserQuizAttemptId", + table: "UserQuizAnswers", + column: "UserQuizAttemptId"); + + migrationBuilder.CreateIndex( + name: "IX_UserQuizAttempts_UserId", + table: "UserQuizAttempts", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_Contact", + table: "Users", + column: "Contact", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_TrackingCode", + table: "Users", + column: "TrackingCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserTotalScores_UserId", + table: "UserTotalScores", + column: "UserId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserKhatmSelections"); + + migrationBuilder.DropTable( + name: "UserQuizAnswers"); + + migrationBuilder.DropTable( + name: "UserTotalScores"); + + migrationBuilder.DropTable( + name: "KhatmItems"); + + migrationBuilder.DropTable( + name: "QuizQuestions"); + + migrationBuilder.DropTable( + name: "UserQuizAttempts"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/backend/Migrations/20260603142557_AddKhatmItemUrl.Designer.cs b/backend/Migrations/20260603142557_AddKhatmItemUrl.Designer.cs new file mode 100644 index 0000000..eb32c0f --- /dev/null +++ b/backend/Migrations/20260603142557_AddKhatmItemUrl.Designer.cs @@ -0,0 +1,381 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using backend.Data; + +#nullable disable + +namespace backend.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260603142557_AddKhatmItemUrl")] + partial class AddKhatmItemUrl + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Url") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("KhatmItems"); + + b.HasData( + new + { + Id = 1, + Points = 5, + Title = "ختم نادعلی", + Url = "https://example.com/khatm/nadali" + }, + new + { + Id = 2, + Points = 10, + Title = "خطبه غدیر", + Url = "https://example.com/khatm/ghadir" + }, + new + { + Id = 3, + Points = 7, + Title = "دعای توسل", + Url = "https://example.com/khatm/tavassol" + }); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CorrectOption") + .HasColumnType("int"); + + b.Property("Option1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option2") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option3") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option4") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("QuestionText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("QuizQuestions"); + + b.HasData( + new + { + Id = 1, + CorrectOption = 1, + Option1 = "سال ۱۰ هجری", + Option2 = "سال ۱۱ هجری", + Option3 = "سال ۹ هجری", + Option4 = "سال ۱۲ هجری", + QuestionText = "غدیر در چه سالی واقع شد؟" + }, + new + { + Id = 2, + CorrectOption = 3, + Option1 = "ابوبکر", + Option2 = "عمر", + Option3 = "علی (ع)", + Option4 = "عثمان", + QuestionText = "پیامبر اکرم (ص) در غدیر چه کسی را به عنوان جانشین معرفی کردند؟" + }, + new + { + Id = 3, + CorrectOption = 2, + Option1 = "مسجد النبی", + Option2 = "غدیر خم", + Option3 = "مکه", + Option4 = "مدینه", + QuestionText = "خطبه غدیر در کجا ایراد شد؟" + }); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TrackingCode") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.HasKey("Id"); + + b.HasIndex("Contact") + .IsUnique(); + + b.HasIndex("TrackingCode") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsSelected") + .HasColumnType("bit"); + + b.Property("KhatmItemId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("KhatmItemId"); + + b.HasIndex("UserId", "KhatmItemId") + .IsUnique(); + + b.ToTable("UserKhatmSelections"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsCorrect") + .HasColumnType("bit"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("SelectedOption") + .HasColumnType("int"); + + b.Property("UserQuizAttemptId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("UserQuizAttemptId"); + + b.ToTable("UserQuizAnswers"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CorrectAnswersCount") + .HasColumnType("int"); + + b.Property("ParticipationPoints") + .HasColumnType("int"); + + b.Property("TotalQuizPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserQuizAttempts"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("TotalPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserTotalScores"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.HasOne("backend.Models.KhatmItem", "KhatmItem") + .WithMany("UserSelections") + .HasForeignKey("KhatmItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.User", "User") + .WithMany("KhatmSelections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KhatmItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.HasOne("backend.Models.QuizQuestion", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.UserQuizAttempt", "QuizAttempt") + .WithMany("Answers") + .HasForeignKey("UserQuizAttemptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + + b.Navigation("QuizAttempt"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("QuizAttempt") + .HasForeignKey("backend.Models.UserQuizAttempt", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("TotalScore") + .HasForeignKey("backend.Models.UserTotalScore", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Navigation("UserSelections"); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Navigation("KhatmSelections"); + + b.Navigation("QuizAttempt"); + + b.Navigation("TotalScore"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Navigation("Answers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Migrations/20260603142557_AddKhatmItemUrl.cs b/backend/Migrations/20260603142557_AddKhatmItemUrl.cs new file mode 100644 index 0000000..e69d52e --- /dev/null +++ b/backend/Migrations/20260603142557_AddKhatmItemUrl.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace backend.Migrations +{ + /// + public partial class AddKhatmItemUrl : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Url", + table: "KhatmItems", + type: "nvarchar(500)", + maxLength: 500, + nullable: true); + + migrationBuilder.UpdateData( + table: "KhatmItems", + keyColumn: "Id", + keyValue: 1, + column: "Url", + value: "https://example.com/khatm/nadali"); + + migrationBuilder.UpdateData( + table: "KhatmItems", + keyColumn: "Id", + keyValue: 2, + column: "Url", + value: "https://example.com/khatm/ghadir"); + + migrationBuilder.UpdateData( + table: "KhatmItems", + keyColumn: "Id", + keyValue: 3, + column: "Url", + value: "https://example.com/khatm/tavassol"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Url", + table: "KhatmItems"); + } + } +} diff --git a/backend/Migrations/20260603144334_UpdateTrackingCodeLength.Designer.cs b/backend/Migrations/20260603144334_UpdateTrackingCodeLength.Designer.cs new file mode 100644 index 0000000..b5f61fb --- /dev/null +++ b/backend/Migrations/20260603144334_UpdateTrackingCodeLength.Designer.cs @@ -0,0 +1,381 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using backend.Data; + +#nullable disable + +namespace backend.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260603144334_UpdateTrackingCodeLength")] + partial class UpdateTrackingCodeLength + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Url") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("KhatmItems"); + + b.HasData( + new + { + Id = 1, + Points = 5, + Title = "ختم نادعلی", + Url = "https://example.com/khatm/nadali" + }, + new + { + Id = 2, + Points = 10, + Title = "خطبه غدیر", + Url = "https://example.com/khatm/ghadir" + }, + new + { + Id = 3, + Points = 7, + Title = "دعای توسل", + Url = "https://example.com/khatm/tavassol" + }); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CorrectOption") + .HasColumnType("int"); + + b.Property("Option1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option2") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option3") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option4") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("QuestionText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("QuizQuestions"); + + b.HasData( + new + { + Id = 1, + CorrectOption = 1, + Option1 = "سال ۱۰ هجری", + Option2 = "سال ۱۱ هجری", + Option3 = "سال ۹ هجری", + Option4 = "سال ۱۲ هجری", + QuestionText = "غدیر در چه سالی واقع شد؟" + }, + new + { + Id = 2, + CorrectOption = 3, + Option1 = "ابوبکر", + Option2 = "عمر", + Option3 = "علی (ع)", + Option4 = "عثمان", + QuestionText = "پیامبر اکرم (ص) در غدیر چه کسی را به عنوان جانشین معرفی کردند؟" + }, + new + { + Id = 3, + CorrectOption = 2, + Option1 = "مسجد النبی", + Option2 = "غدیر خم", + Option3 = "مکه", + Option4 = "مدینه", + QuestionText = "خطبه غدیر در کجا ایراد شد؟" + }); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TrackingCode") + .IsRequired() + .HasMaxLength(4) + .HasColumnType("nvarchar(4)"); + + b.HasKey("Id"); + + b.HasIndex("Contact") + .IsUnique(); + + b.HasIndex("TrackingCode") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsSelected") + .HasColumnType("bit"); + + b.Property("KhatmItemId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("KhatmItemId"); + + b.HasIndex("UserId", "KhatmItemId") + .IsUnique(); + + b.ToTable("UserKhatmSelections"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsCorrect") + .HasColumnType("bit"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("SelectedOption") + .HasColumnType("int"); + + b.Property("UserQuizAttemptId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("UserQuizAttemptId"); + + b.ToTable("UserQuizAnswers"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CorrectAnswersCount") + .HasColumnType("int"); + + b.Property("ParticipationPoints") + .HasColumnType("int"); + + b.Property("TotalQuizPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserQuizAttempts"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("TotalPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserTotalScores"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.HasOne("backend.Models.KhatmItem", "KhatmItem") + .WithMany("UserSelections") + .HasForeignKey("KhatmItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.User", "User") + .WithMany("KhatmSelections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KhatmItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.HasOne("backend.Models.QuizQuestion", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.UserQuizAttempt", "QuizAttempt") + .WithMany("Answers") + .HasForeignKey("UserQuizAttemptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + + b.Navigation("QuizAttempt"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("QuizAttempt") + .HasForeignKey("backend.Models.UserQuizAttempt", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("TotalScore") + .HasForeignKey("backend.Models.UserTotalScore", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Navigation("UserSelections"); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Navigation("KhatmSelections"); + + b.Navigation("QuizAttempt"); + + b.Navigation("TotalScore"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Navigation("Answers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Migrations/20260603144334_UpdateTrackingCodeLength.cs b/backend/Migrations/20260603144334_UpdateTrackingCodeLength.cs new file mode 100644 index 0000000..356b17b --- /dev/null +++ b/backend/Migrations/20260603144334_UpdateTrackingCodeLength.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace backend.Migrations +{ + /// + public partial class UpdateTrackingCodeLength : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // First, update existing tracking codes to 4-digit numeric values + // Using a hash-based approach to generate unique 4-digit codes from existing ones + migrationBuilder.Sql(@" + DECLARE @Id int, @RowNum int, @NewCode nvarchar(4) + DECLARE code_cursor CURSOR FOR + SELECT Id, ROW_NUMBER() OVER (ORDER BY Id) AS RowNum + FROM Users + OPEN code_cursor + FETCH NEXT FROM code_cursor INTO @Id, @RowNum + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @NewCode = RIGHT('0000' + CAST((@RowNum + 1000) AS nvarchar(4)), 4) + UPDATE Users SET TrackingCode = @NewCode WHERE Id = @Id + FETCH NEXT FROM code_cursor INTO @Id, @RowNum + END + CLOSE code_cursor + DEALLOCATE code_cursor + "); + + migrationBuilder.AlterColumn( + name: "TrackingCode", + table: "Users", + type: "nvarchar(4)", + maxLength: 4, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(8)", + oldMaxLength: 8); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "TrackingCode", + table: "Users", + type: "nvarchar(8)", + maxLength: 8, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(4)", + oldMaxLength: 4); + } + } +} diff --git a/backend/Migrations/20260603144804_SeedQuizQuestions.Designer.cs b/backend/Migrations/20260603144804_SeedQuizQuestions.Designer.cs new file mode 100644 index 0000000..6966450 --- /dev/null +++ b/backend/Migrations/20260603144804_SeedQuizQuestions.Designer.cs @@ -0,0 +1,451 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using backend.Data; + +#nullable disable + +namespace backend.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260603144804_SeedQuizQuestions")] + partial class SeedQuizQuestions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Url") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("KhatmItems"); + + b.HasData( + new + { + Id = 1, + Points = 5, + Title = "ختم نادعلی", + Url = "https://example.com/khatm/nadali" + }, + new + { + Id = 2, + Points = 10, + Title = "خطبه غدیر", + Url = "https://example.com/khatm/ghadir" + }, + new + { + Id = 3, + Points = 7, + Title = "دعای توسل", + Url = "https://example.com/khatm/tavassol" + }); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CorrectOption") + .HasColumnType("int"); + + b.Property("Option1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option2") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option3") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option4") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("QuestionText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("QuizQuestions"); + + b.HasData( + new + { + Id = 1, + CorrectOption = 4, + Option1 = "آيه 31 سوره نور", + Option2 = "آيه 58 – 60 سوره نور", + Option3 = "آيه 59 – 60 سوره احزاب", + Option4 = "آيه 59 – 60 سوره نساء", + QuestionText = "كدام يك از آيات زير مربوط به حجاب نمي باشد؟" + }, + new + { + Id = 2, + CorrectOption = 3, + Option1 = "پوشش – عفاف", + Option2 = "ترك نظر – پوشش", + Option3 = "ترك نظر – پاكدامني", + Option4 = "پوشش – پاكدامني", + QuestionText = "اين قسمت از آيه 31 سوره نور « قل للمومنات يغضضن من ابصار هن و يحفظن فروجهن» بيانگر كدام دو تكليف است؟" + }, + new + { + Id = 3, + CorrectOption = 1, + Option1 = "زنان سالخورده اي كه اميدي به ازدواج ايشان نمي باشد", + Option2 = "بردگان و مملوكان", + Option3 = "طفيلياني كه نيازي به زن ندارند", + Option4 = "اطفال نابالغ ولو مميز نزديك به بلوغ", + QuestionText = "منظور از (( قواعد )) در سوره نور آيات 58-60 چه كساني هستند؟" + }, + new + { + Id = 4, + CorrectOption = 3, + Option1 = "در مورد ثمرات ولایت و محبت اهل‌بیت", + Option2 = "در مورد جنبه غضب الهی", + Option3 = "در مورد امام مهدی علیه‌السلام", + Option4 = "اقرار لسانی", + QuestionText = "هشتمین بخش خطبه غدیر در چه موضوعی است؟" + }, + new + { + Id = 5, + CorrectOption = 1, + Option1 = "این لقب به دستور مستقیم از جانب خداوند به ایشان داده شد", + Option2 = "پیامبر اکرم، خودشان این لقب را انتخاب کردند", + Option3 = "مردم به علت فضائل بسیار ایشان، این لقب را انتخاب کردند", + Option4 = "به درخواست خود ایشان بود", + QuestionText = "لقب «امیرالمؤمنین» توسط چه کسی به مولا علی علیه‌السلام اطلاق شد؟" + }, + new + { + Id = 6, + CorrectOption = 3, + Option1 = "کسانی که خبر اسلام به آنان نرسیده است", + Option2 = "کسانی که در مدیریت زمان ضعیف هستند", + Option3 = "دشمنان آل محمد (صلی‌الله‌علیه‌وآله)", + Option4 = "اهل کتاب", + QuestionText = "طبق فرمایش پیامبر اکرم در غدیر، عبارت «إِنَّ الإِنسَانَ لَفِي خُسْرٍ»، «انسان در زیان است» درباره چه کسانی است؟" + }, + new + { + Id = 7, + CorrectOption = 3, + Option1 = "سوره کهف آیه ۲۸", + Option2 = "سوره انعام آیه ۶۱", + Option3 = "سوره زمر ۵۶", + Option4 = "سوره طه ۴۵", + QuestionText = "در کدام آیه از قرآن مجید خداوند می فرماید که درباره علی تفریط و کوتاهی نکنید؟" + }, + new + { + Id = 8, + CorrectOption = 2, + Option1 = "تعيين امامت ،علی عليه السلام", + Option2 = "تبليغ امامت، علی عليه السلام", + Option3 = "تعيين و تبليغ امامت، علی عليه السلام", + Option4 = "سپردن تعیین امامت به مردم", + QuestionText = "طبق آیه 67 سوره مائده وظيفه رسول خدا در برابر امامت چيست و آيه در شأن چه كسی نازل شده است؟\n«يَا أَيُّهَا الرَّسُولُ بَلِّغْ مَا أُنْزِلَ إِلَيْكَ مِنْ رَبِّكَ وَإِنْ لَمْ تَفْعَلْ فَمَا بَلَّغْتَ رِسَالَتَهُ ...»" + }, + new + { + Id = 9, + CorrectOption = 1, + Option1 = "عید غدیر، روز تبلیغ و معرفی امیرالمومنین به عنوان جانشین پیامبر", + Option2 = "روز جمعه", + Option3 = "عید قربان، حج تمتع", + Option4 = "عید فطر، پایان یک ماه بندگی", + QuestionText = "طبق روایت امام رضا علیه السلام برترین عید امت اسلامی کدام است؟" + }, + new + { + Id = 10, + CorrectOption = 4, + Option1 = "با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) توسط پیامبر اکرم(صلی الله علیه و آله) دین کامل گشت", + Option2 = "خداوند با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) و یازده فرزند پاکشان از اسلام اعلام رضایت فرمودند", + Option3 = "شرط اینکه دین شخص مسلمان مورد رضایت خداوند متعال قرار گیرد پذیرش ولایت و امامت ائمه (علیهم السلام) است", + Option4 = "امامت از فروع دین بوده و قبول یا عدم پذیرش آن لطمه‌ای به آن شخص وارد نمی‌کند", + QuestionText = "با توجه به مطلبی که در آیه شریفه «الْيَوْمَ أَكْمَلْتُ لَكُمْ دِينَكُمْ وَ أَتْمَمْتُ عَلَيْكُمْ نِعْمَتِي وَ رَضِيتُ لَكُمُ الْإِسْلامَ دِينا» در روز غدیر نازل گشته است. کدام گزینه صحیح نمی‌باشد؟" + }); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TrackingCode") + .IsRequired() + .HasMaxLength(4) + .HasColumnType("nvarchar(4)"); + + b.HasKey("Id"); + + b.HasIndex("Contact") + .IsUnique(); + + b.HasIndex("TrackingCode") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsSelected") + .HasColumnType("bit"); + + b.Property("KhatmItemId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("KhatmItemId"); + + b.HasIndex("UserId", "KhatmItemId") + .IsUnique(); + + b.ToTable("UserKhatmSelections"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsCorrect") + .HasColumnType("bit"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("SelectedOption") + .HasColumnType("int"); + + b.Property("UserQuizAttemptId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("UserQuizAttemptId"); + + b.ToTable("UserQuizAnswers"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CorrectAnswersCount") + .HasColumnType("int"); + + b.Property("ParticipationPoints") + .HasColumnType("int"); + + b.Property("TotalQuizPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserQuizAttempts"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("TotalPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserTotalScores"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.HasOne("backend.Models.KhatmItem", "KhatmItem") + .WithMany("UserSelections") + .HasForeignKey("KhatmItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.User", "User") + .WithMany("KhatmSelections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KhatmItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.HasOne("backend.Models.QuizQuestion", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.UserQuizAttempt", "QuizAttempt") + .WithMany("Answers") + .HasForeignKey("UserQuizAttemptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + + b.Navigation("QuizAttempt"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("QuizAttempt") + .HasForeignKey("backend.Models.UserQuizAttempt", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("TotalScore") + .HasForeignKey("backend.Models.UserTotalScore", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Navigation("UserSelections"); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Navigation("KhatmSelections"); + + b.Navigation("QuizAttempt"); + + b.Navigation("TotalScore"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Navigation("Answers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Migrations/20260603144804_SeedQuizQuestions.cs b/backend/Migrations/20260603144804_SeedQuizQuestions.cs new file mode 100644 index 0000000..649e167 --- /dev/null +++ b/backend/Migrations/20260603144804_SeedQuizQuestions.cs @@ -0,0 +1,111 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace backend.Migrations +{ + /// + public partial class SeedQuizQuestions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 1, + columns: new[] { "CorrectOption", "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[] { 4, "آيه 31 سوره نور", "آيه 58 – 60 سوره نور", "آيه 59 – 60 سوره احزاب", "آيه 59 – 60 سوره نساء", "كدام يك از آيات زير مربوط به حجاب نمي باشد؟" }); + + migrationBuilder.UpdateData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 2, + columns: new[] { "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[] { "پوشش – عفاف", "ترك نظر – پوشش", "ترك نظر – پاكدامني", "پوشش – پاكدامني", "اين قسمت از آيه 31 سوره نور « قل للمومنات يغضضن من ابصار هن و يحفظن فروجهن» بيانگر كدام دو تكليف است؟" }); + + migrationBuilder.UpdateData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 3, + columns: new[] { "CorrectOption", "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[] { 1, "زنان سالخورده اي كه اميدي به ازدواج ايشان نمي باشد", "بردگان و مملوكان", "طفيلياني كه نيازي به زن ندارند", "اطفال نابالغ ولو مميز نزديك به بلوغ", "منظور از (( قواعد )) در سوره نور آيات 58-60 چه كساني هستند؟" }); + + migrationBuilder.InsertData( + table: "QuizQuestions", + columns: new[] { "Id", "CorrectOption", "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[,] + { + { 4, 3, "در مورد ثمرات ولایت و محبت اهل‌بیت", "در مورد جنبه غضب الهی", "در مورد امام مهدی علیه‌السلام", "اقرار لسانی", "هشتمین بخش خطبه غدیر در چه موضوعی است؟" }, + { 5, 1, "این لقب به دستور مستقیم از جانب خداوند به ایشان داده شد", "پیامبر اکرم، خودشان این لقب را انتخاب کردند", "مردم به علت فضائل بسیار ایشان، این لقب را انتخاب کردند", "به درخواست خود ایشان بود", "لقب «امیرالمؤمنین» توسط چه کسی به مولا علی علیه‌السلام اطلاق شد؟" }, + { 6, 3, "کسانی که خبر اسلام به آنان نرسیده است", "کسانی که در مدیریت زمان ضعیف هستند", "دشمنان آل محمد (صلی‌الله‌علیه‌وآله)", "اهل کتاب", "طبق فرمایش پیامبر اکرم در غدیر، عبارت «إِنَّ الإِنسَانَ لَفِي خُسْرٍ»، «انسان در زیان است» درباره چه کسانی است؟" }, + { 7, 3, "سوره کهف آیه ۲۸", "سوره انعام آیه ۶۱", "سوره زمر ۵۶", "سوره طه ۴۵", "در کدام آیه از قرآن مجید خداوند می فرماید که درباره علی تفریط و کوتاهی نکنید؟" }, + { 8, 2, "تعيين امامت ،علی عليه السلام", "تبليغ امامت، علی عليه السلام", "تعيين و تبليغ امامت، علی عليه السلام", "سپردن تعیین امامت به مردم", "طبق آیه 67 سوره مائده وظيفه رسول خدا در برابر امامت چيست و آيه در شأن چه كسی نازل شده است؟\n«يَا أَيُّهَا الرَّسُولُ بَلِّغْ مَا أُنْزِلَ إِلَيْكَ مِنْ رَبِّكَ وَإِنْ لَمْ تَفْعَلْ فَمَا بَلَّغْتَ رِسَالَتَهُ ...»" }, + { 9, 1, "عید غدیر، روز تبلیغ و معرفی امیرالمومنین به عنوان جانشین پیامبر", "روز جمعه", "عید قربان، حج تمتع", "عید فطر، پایان یک ماه بندگی", "طبق روایت امام رضا علیه السلام برترین عید امت اسلامی کدام است؟" }, + { 10, 4, "با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) توسط پیامبر اکرم(صلی الله علیه و آله) دین کامل گشت", "خداوند با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) و یازده فرزند پاکشان از اسلام اعلام رضایت فرمودند", "شرط اینکه دین شخص مسلمان مورد رضایت خداوند متعال قرار گیرد پذیرش ولایت و امامت ائمه (علیهم السلام) است", "امامت از فروع دین بوده و قبول یا عدم پذیرش آن لطمه‌ای به آن شخص وارد نمی‌کند", "با توجه به مطلبی که در آیه شریفه «الْيَوْمَ أَكْمَلْتُ لَكُمْ دِينَكُمْ وَ أَتْمَمْتُ عَلَيْكُمْ نِعْمَتِي وَ رَضِيتُ لَكُمُ الْإِسْلامَ دِينا» در روز غدیر نازل گشته است. کدام گزینه صحیح نمی‌باشد؟" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 4); + + migrationBuilder.DeleteData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 5); + + migrationBuilder.DeleteData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 6); + + migrationBuilder.DeleteData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 7); + + migrationBuilder.DeleteData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 8); + + migrationBuilder.DeleteData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 9); + + migrationBuilder.DeleteData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 10); + + migrationBuilder.UpdateData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 1, + columns: new[] { "CorrectOption", "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[] { 1, "سال ۱۰ هجری", "سال ۱۱ هجری", "سال ۹ هجری", "سال ۱۲ هجری", "غدیر در چه سالی واقع شد؟" }); + + migrationBuilder.UpdateData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 2, + columns: new[] { "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[] { "ابوبکر", "عمر", "علی (ع)", "عثمان", "پیامبر اکرم (ص) در غدیر چه کسی را به عنوان جانشین معرفی کردند؟" }); + + migrationBuilder.UpdateData( + table: "QuizQuestions", + keyColumn: "Id", + keyValue: 3, + columns: new[] { "CorrectOption", "Option1", "Option2", "Option3", "Option4", "QuestionText" }, + values: new object[] { 2, "مسجد النبی", "غدیر خم", "مکه", "مدینه", "خطبه غدیر در کجا ایراد شد؟" }); + } + } +} diff --git a/backend/Migrations/AppDbContextModelSnapshot.cs b/backend/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..77e2938 --- /dev/null +++ b/backend/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,448 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using backend.Data; + +#nullable disable + +namespace backend.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Points") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Url") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("KhatmItems"); + + b.HasData( + new + { + Id = 1, + Points = 5, + Title = "ختم نادعلی", + Url = "https://example.com/khatm/nadali" + }, + new + { + Id = 2, + Points = 10, + Title = "خطبه غدیر", + Url = "https://example.com/khatm/ghadir" + }, + new + { + Id = 3, + Points = 7, + Title = "دعای توسل", + Url = "https://example.com/khatm/tavassol" + }); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CorrectOption") + .HasColumnType("int"); + + b.Property("Option1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option2") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option3") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Option4") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("QuestionText") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.ToTable("QuizQuestions"); + + b.HasData( + new + { + Id = 1, + CorrectOption = 3, + Option1 = "در مورد ثمرات ولایت و محبت اهل‌بیت", + Option2 = "در مورد جنبه غضب الهی", + Option3 = "در مورد امام مهدی علیه‌السلام", + Option4 = "اقرار لسانی", + QuestionText = "هشتمین بخش خطبه غدیر در چه موضوعی است؟" + }, + new + { + Id = 2, + CorrectOption = 1, + Option1 = "این لقب به دستور مستقیم از جانب خداوند به ایشان داده شد", + Option2 = "پیامبر اکرم، خودشان این لقب را انتخاب کردند", + Option3 = "مردم به علت فضائل بسیار ایشان، این لقب را انتخاب کردند", + Option4 = "به درخواست خود ایشان بود", + QuestionText = "لقب «امیرالمؤمنین» توسط چه کسی به مولا علی علیه‌السلام اطلاق شد؟" + }, + new + { + Id = 3, + CorrectOption = 3, + Option1 = "کسانی که خبر اسلام به آنان نرسیده است", + Option2 = "کسانی که در مدیریت زمان ضعیف هستند", + Option3 = "دشمنان آل محمد (صلی‌الله‌علیه‌وآله)", + Option4 = "اهل کتاب", + QuestionText = "طبق فرمایش پیامبر اکرم در غدیر، عبارت «إِنَّ الإِنسَانَ لَفِي خُسْرٍ»، «انسان در زیان است» درباره چه کسانی است؟" + }, + new + { + Id = 4, + CorrectOption = 4, + Option1 = "آيه 31 سوره نور", + Option2 = "آيه 58 – 60 سوره نور", + Option3 = "آيه 59 – 60 سوره احزاب", + Option4 = "آيه 59 – 60 سوره نساء", + QuestionText = "كدام يك از آيات زير مربوط به حجاب نمي باشد؟" + }, + new + { + Id = 5, + CorrectOption = 3, + Option1 = "پوشش – عفاف", + Option2 = "ترك نظر – پوشش", + Option3 = "ترك نظر – پاكدامني", + Option4 = "پوشش – پاكدامني", + QuestionText = "اين قسمت از آيه 31 سوره نور « قل للمومنات يغضضن من ابصار هن و يحفظن فروجهن» بيانگر كدام دو تكليف است؟" + }, + new + { + Id = 6, + CorrectOption = 1, + Option1 = "زنان سالخورده اي كه اميدي به ازدواج ايشان نمي باشد", + Option2 = "بردگان و مملوكان", + Option3 = "طفيلياني كه نيازي به زن ندارند", + Option4 = "اطفال نابالغ ولو مميز نزديك به بلوغ", + QuestionText = "منظور از (( قواعد )) در سوره نور آيات 58-60 چه كساني هستند؟" + }, + new + { + Id = 7, + CorrectOption = 3, + Option1 = "سوره کهف آیه ۲۸", + Option2 = "سوره انعام آیه ۶۱", + Option3 = "سوره زمر ۵۶", + Option4 = "سوره طه ۴۵", + QuestionText = "در کدام آیه از قرآن مجید خداوند می فرماید که درباره علی تفریط و کوتاهی نکنید؟" + }, + new + { + Id = 8, + CorrectOption = 2, + Option1 = "تعيين امامت ،علی عليه السلام", + Option2 = "تبليغ امامت، علی عليه السلام", + Option3 = "تعيين و تبليغ امامت، علی عليه السلام", + Option4 = "سپردن تعیین امامت به مردم", + QuestionText = "طبق آیه 67 سوره مائده وظيفه رسول خدا در برابر امامت چيست و آيه در شأن چه كسی نازل شده است؟\n«يَا أَيُّهَا الرَّسُولُ بَلِّغْ مَا أُنْزِلَ إِلَيْكَ مِنْ رَبِّكَ وَإِنْ لَمْ تَفْعَلْ فَمَا بَلَّغْتَ رِسَالَتَهُ ...»" + }, + new + { + Id = 9, + CorrectOption = 1, + Option1 = "عید غدیر، روز تبلیغ و معرفی امیرالمومنین به عنوان جانشین پیامبر", + Option2 = "روز جمعه", + Option3 = "عید قربان، حج تمتع", + Option4 = "عید فطر، پایان یک ماه بندگی", + QuestionText = "طبق روایت امام رضا علیه السلام برترین عید امت اسلامی کدام است؟" + }, + new + { + Id = 10, + CorrectOption = 4, + Option1 = "با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) توسط پیامبر اکرم(صلی الله علیه و آله) دین کامل گشت", + Option2 = "خداوند با ابلاغ ولایت و امامت امیرالمومنین(علیه السلام) و یازده فرزند پاکشان از اسلام اعلام رضایت فرمودند", + Option3 = "شرط اینکه دین شخص مسلمان مورد رضایت خداوند متعال قرار گیرد پذیرش ولایت و امامت ائمه (علیهم السلام) است", + Option4 = "امامت از فروع دین بوده و قبول یا عدم پذیرش آن لطمه‌ای به آن شخص وارد نمی‌کند", + QuestionText = "با توجه به مطلبی که در آیه شریفه «الْيَوْمَ أَكْمَلْتُ لَكُمْ دِينَكُمْ وَ أَتْمَمْتُ عَلَيْكُمْ نِعْمَتِي وَ رَضِيتُ لَكُمُ الْإِسْلامَ دِينا» در روز غدیر نازل گشته است. کدام گزینه صحیح نمی‌باشد؟" + }); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TrackingCode") + .IsRequired() + .HasMaxLength(4) + .HasColumnType("nvarchar(4)"); + + b.HasKey("Id"); + + b.HasIndex("Contact") + .IsUnique(); + + b.HasIndex("TrackingCode") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsSelected") + .HasColumnType("bit"); + + b.Property("KhatmItemId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("KhatmItemId"); + + b.HasIndex("UserId", "KhatmItemId") + .IsUnique(); + + b.ToTable("UserKhatmSelections"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsCorrect") + .HasColumnType("bit"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("SelectedOption") + .HasColumnType("int"); + + b.Property("UserQuizAttemptId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("UserQuizAttemptId"); + + b.ToTable("UserQuizAnswers"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CorrectAnswersCount") + .HasColumnType("int"); + + b.Property("ParticipationPoints") + .HasColumnType("int"); + + b.Property("TotalQuizPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserQuizAttempts"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("TotalPoints") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserTotalScores"); + }); + + modelBuilder.Entity("backend.Models.UserKhatmSelection", b => + { + b.HasOne("backend.Models.KhatmItem", "KhatmItem") + .WithMany("UserSelections") + .HasForeignKey("KhatmItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.User", "User") + .WithMany("KhatmSelections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("KhatmItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAnswer", b => + { + b.HasOne("backend.Models.QuizQuestion", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("backend.Models.UserQuizAttempt", "QuizAttempt") + .WithMany("Answers") + .HasForeignKey("UserQuizAttemptId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + + b.Navigation("QuizAttempt"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("QuizAttempt") + .HasForeignKey("backend.Models.UserQuizAttempt", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.UserTotalScore", b => + { + b.HasOne("backend.Models.User", "User") + .WithOne("TotalScore") + .HasForeignKey("backend.Models.UserTotalScore", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("backend.Models.KhatmItem", b => + { + b.Navigation("UserSelections"); + }); + + modelBuilder.Entity("backend.Models.QuizQuestion", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("backend.Models.User", b => + { + b.Navigation("KhatmSelections"); + + b.Navigation("QuizAttempt"); + + b.Navigation("TotalScore"); + }); + + modelBuilder.Entity("backend.Models.UserQuizAttempt", b => + { + b.Navigation("Answers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Models/AppConfig.cs b/backend/Models/AppConfig.cs new file mode 100644 index 0000000..3990fba --- /dev/null +++ b/backend/Models/AppConfig.cs @@ -0,0 +1,23 @@ +namespace backend.Models; + +public class AppConfig +{ + public string SiteTitle { get; set; } = string.Empty; + public string Subtitle { get; set; } = string.Empty; + public List Channels { get; set; } = new(); + public string ShortText { get; set; } = string.Empty; + public QuizConfig Quiz { get; set; } = new(); + public string AdminPassword { get; set; } = string.Empty; +} + +public class ChannelInfo +{ + public string Name { get; set; } = string.Empty; + public string Url { get; set; } = string.Empty; +} + +public class QuizConfig +{ + public int ParticipationPoints { get; set; } + public int CorrectAnswerPoints { get; set; } +} diff --git a/backend/Models/KhatmItem.cs b/backend/Models/KhatmItem.cs new file mode 100644 index 0000000..bfa5595 --- /dev/null +++ b/backend/Models/KhatmItem.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace backend.Models; + +public class KhatmItem +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + [MaxLength(200)] + public string Title { get; set; } = string.Empty; + + [MaxLength(500)] + public string? Url { get; set; } + + [Required] + public int Points { get; set; } + + // Navigation + public ICollection UserSelections { get; set; } = new List(); +} diff --git a/backend/Models/QuizQuestion.cs b/backend/Models/QuizQuestion.cs new file mode 100644 index 0000000..693a6de --- /dev/null +++ b/backend/Models/QuizQuestion.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace backend.Models; + +public class QuizQuestion +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + [MaxLength(500)] + public string QuestionText { get; set; } = string.Empty; + + [Required] + [MaxLength(200)] + public string Option1 { get; set; } = string.Empty; + + [Required] + [MaxLength(200)] + public string Option2 { get; set; } = string.Empty; + + [Required] + [MaxLength(200)] + public string Option3 { get; set; } = string.Empty; + + [Required] + [MaxLength(200)] + public string Option4 { get; set; } = string.Empty; + + [Required] + public int CorrectOption { get; set; } // 1-4 + + // Navigation + public ICollection Answers { get; set; } = new List(); +} diff --git a/backend/Models/User.cs b/backend/Models/User.cs new file mode 100644 index 0000000..d964569 --- /dev/null +++ b/backend/Models/User.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace backend.Models; + +public class User +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + [MaxLength(100)] + public string FullName { get; set; } = string.Empty; + + [Required] + [MaxLength(50)] + public string Contact { get; set; } = string.Empty; + + [Required] + [MaxLength(4)] + public string TrackingCode { get; set; } = string.Empty; + + // Navigation properties + public UserQuizAttempt? QuizAttempt { get; set; } + public ICollection KhatmSelections { get; set; } = new List(); + public UserTotalScore? TotalScore { get; set; } +} diff --git a/backend/Models/UserKhatmSelection.cs b/backend/Models/UserKhatmSelection.cs new file mode 100644 index 0000000..8df6eb4 --- /dev/null +++ b/backend/Models/UserKhatmSelection.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace backend.Models; + +public class UserKhatmSelection +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + public int UserId { get; set; } + + [Required] + public int KhatmItemId { get; set; } + + public bool IsSelected { get; set; } + + public DateTime LastModified { get; set; } = DateTime.UtcNow; + + // Navigation + [ForeignKey(nameof(UserId))] + public User User { get; set; } = null!; + + [ForeignKey(nameof(KhatmItemId))] + public KhatmItem KhatmItem { get; set; } = null!; +} diff --git a/backend/Models/UserQuizAnswer.cs b/backend/Models/UserQuizAnswer.cs new file mode 100644 index 0000000..f2a86c3 --- /dev/null +++ b/backend/Models/UserQuizAnswer.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace backend.Models; + +public class UserQuizAnswer +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + public int UserQuizAttemptId { get; set; } + + [Required] + public int QuestionId { get; set; } + + [Required] + public int SelectedOption { get; set; } + + public bool IsCorrect { get; set; } + + // Navigation + [ForeignKey(nameof(UserQuizAttemptId))] + public UserQuizAttempt QuizAttempt { get; set; } = null!; + + [ForeignKey(nameof(QuestionId))] + public QuizQuestion Question { get; set; } = null!; +} diff --git a/backend/Models/UserQuizAttempt.cs b/backend/Models/UserQuizAttempt.cs new file mode 100644 index 0000000..809072c --- /dev/null +++ b/backend/Models/UserQuizAttempt.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace backend.Models; + +public class UserQuizAttempt +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + public int UserId { get; set; } + + public int ParticipationPoints { get; set; } + + public int CorrectAnswersCount { get; set; } + + public int TotalQuizPoints { get; set; } + + public DateTime CompletedAt { get; set; } = DateTime.UtcNow; + + // Navigation + [ForeignKey(nameof(UserId))] + public User User { get; set; } = null!; + + public ICollection Answers { get; set; } = new List(); +} diff --git a/backend/Models/UserTotalScore.cs b/backend/Models/UserTotalScore.cs new file mode 100644 index 0000000..5e65d29 --- /dev/null +++ b/backend/Models/UserTotalScore.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace backend.Models; + +public class UserTotalScore +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + public int UserId { get; set; } + + public int TotalPoints { get; set; } + + // Navigation + [ForeignKey(nameof(UserId))] + public User User { get; set; } = null!; +} diff --git a/backend/Program.cs b/backend/Program.cs new file mode 100644 index 0000000..7ddced2 --- /dev/null +++ b/backend/Program.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using backend.Data; +using backend.Models; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container +builder.Services.AddControllers(); + +// Configure Entity Framework with SQL Server +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); + +// Bind AppConfig from appsettings.json +builder.Services.Configure( + builder.Configuration.GetSection("AppConfig")); + +// Configure CORS +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowFrontend", policy => + { + policy.WithOrigins("http://localhost:5173", "http://localhost:3000") + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +// Configure serving the built SPA static files (used in Production) +builder.Services.AddSpaStaticFiles(configuration => +{ + configuration.RootPath = "ClientApp/dist"; +}); + +var app = builder.Build(); + +app.UseCors("AllowFrontend"); +app.MapControllers(); + +// Auto-migrate database on startup +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); +} + +// Configure SPA serving +if (!app.Environment.IsDevelopment()) +{ + // In Production/Staging, serve the pre-built frontend from ClientApp/dist + app.UseSpaStaticFiles(); + app.UseSpa(spa => + { + spa.Options.SourcePath = "ClientApp"; + }); +} +// In Development: +// - The backend serves only the API at http://localhost:5000 +// - The frontend is served by Vite dev server at http://localhost:5173 +// - Vite proxies /api requests to the backend +// - Run: cd backend\ClientApp && npm run dev (for hot-reload frontend) +// - Or open http://localhost:5000 after building the frontend + +app.Run(); diff --git a/backend/Properties/launchSettings.json b/backend/Properties/launchSettings.json new file mode 100644 index 0000000..289108f --- /dev/null +++ b/backend/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5000" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53255/", + "sslPort": 44327 + } + } +} \ No newline at end of file diff --git a/backend/appsettings.json b/backend/appsettings.json new file mode 100644 index 0000000..1669d10 --- /dev/null +++ b/backend/appsettings.json @@ -0,0 +1,32 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server=.;Database=GhadirContestDB;Trusted_Connection=True;TrustServerCertificate=True" + }, + "AppConfig": { + "SiteTitle": "مسابقه بزرگ غدیر", + "Subtitle": "هیئت ثارالله با همکاری کانال شهر من کهک برگزار می کند.", + "Channels": [ + { + "Name": "هیئت ثارالله", + "Url": "https://eitaa.com/saralllah_kahak" + }, + { + "Name": "کانال کهک", + "Url": "https://eitaa.com/kahakcity" + } + ], + "ShortText": "به مسابقه بزرگ غدیر خوش آمدید. با شرکت در این مسابقه، ضمن آشنایی بیشتر با واقعه غدیر، می‌توانید در قرعه‌کشی جوایز ارزنده شرکت کنید.", + "Quiz": { + "ParticipationPoints": 10, + "CorrectAnswerPoints": 5 + }, + "AdminPassword": "ghadir1403" + } +} \ No newline at end of file diff --git a/backend/backend.csproj b/backend/backend.csproj new file mode 100644 index 0000000..c6d494e --- /dev/null +++ b/backend/backend.csproj @@ -0,0 +1,59 @@ + + + + net8.0 + enable + enable + ClientApp\ + $(DefaultItemExcludes);$(SpaRoot)node_modules\** + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %(DistFiles.Identity) + PreserveNewest + true + + + + + diff --git a/backend/backend.http b/backend/backend.http new file mode 100644 index 0000000..51f3bcf --- /dev/null +++ b/backend/backend.http @@ -0,0 +1,6 @@ +@backend_HostAddress = http://localhost:5004 + +GET {{backend_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1c2b11c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,47 @@ +version: "3.8" + +services: + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + container_name: ghadir-db + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=Ghadir@1403 + - MSSQL_PID=Express + ports: + - "1433:1433" + volumes: + - sqlserver-data:/var/opt/mssql + networks: + - ghadir-network + healthcheck: + test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "Ghadir@1403" -C -Q "SELECT 1" || exit 1 + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: ghadir-backend + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ASPNETCORE_URLS=http://+:8080 + - ConnectionStrings__DefaultConnection=Server=sqlserver;Database=GhadirContestDB;User Id=sa;Password=Ghadir@1403;TrustServerCertificate=True + ports: + - "5000:8080" + depends_on: + sqlserver: + condition: service_healthy + networks: + - ghadir-network + restart: unless-stopped + +networks: + ghadir-network: + driver: bridge + +volumes: + sqlserver-data: