From e1494ca941451a29466d7a83f7d7fc26cf5ccf0b Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 19 Nov 2024 12:50:53 +0100 Subject: [PATCH] vinvoor: lint & format update --- .githooks/pre-commit | 16 + .githooks/vinvoor_format_lint | 20 - .../{vinvoor_format_lint.yml => vinvoor.yml} | 20 +- vinvoor/.eslintrc.cjs | 18 - vinvoor/.prettierrc | 4 - vinvoor/eslint.config.mjs | 42 +- vinvoor/package.json | 6 +- vinvoor/pnpm-lock.yaml | 842 +++++++++++++++++- vinvoor/src/App.tsx | 34 +- vinvoor/src/WelcomePage.tsx | 6 +- vinvoor/src/auth/Login.tsx | 7 +- vinvoor/src/auth/Logout.tsx | 7 +- vinvoor/src/cards/Cards.tsx | 4 +- vinvoor/src/cards/CardsAdd.tsx | 60 +- vinvoor/src/cards/CardsDelete.tsx | 16 +- vinvoor/src/cards/CardsEmpty.tsx | 4 +- vinvoor/src/cards/CardsTable.tsx | 67 +- vinvoor/src/cards/CardsTableBody.tsx | 15 +- vinvoor/src/cards/CardsTableHead.tsx | 11 +- vinvoor/src/cards/CardsTableToolbar.tsx | 44 +- vinvoor/src/cards/CircularTimeProgress.tsx | 2 +- vinvoor/src/components/BrowserView.tsx | 9 +- vinvoor/src/components/DarkModeToggle.tsx | 4 +- vinvoor/src/components/LoadingSkeleton.tsx | 14 +- vinvoor/src/components/ProtectedRoute.tsx | 5 +- vinvoor/src/components/TypographyG.tsx | 7 +- vinvoor/src/components/UnstyledLink.tsx | 7 +- vinvoor/src/errors/ErrorPage.tsx | 17 +- vinvoor/src/footer/Footer.tsx | 9 +- vinvoor/src/hooks/admin/useAdminDays.ts | 26 +- vinvoor/src/hooks/admin/useAdminSeason.ts | 25 +- vinvoor/src/hooks/useCard.ts | 16 +- vinvoor/src/hooks/useLeaderboard.ts | 15 +- vinvoor/src/hooks/useScan.ts | 10 +- vinvoor/src/hooks/useSeasons.ts | 16 +- vinvoor/src/hooks/useSettings.ts | 18 +- vinvoor/src/hooks/useUser.ts | 19 +- vinvoor/src/hooks/useVersion.ts | 10 +- vinvoor/src/leaderboard/Leaderboard.tsx | 6 +- .../src/leaderboard/LeaderboardTableBody.tsx | 40 +- .../leaderboard/LeaderboardTableToolbar.tsx | 4 +- vinvoor/src/main.tsx | 18 +- vinvoor/src/navbar/NavBar.tsx | 4 +- vinvoor/src/navbar/NavBarLogo.tsx | 19 +- vinvoor/src/navbar/NavBarPages.tsx | 7 +- vinvoor/src/navbar/NavBarSandwich.tsx | 11 +- vinvoor/src/navbar/NavBarSeasons.tsx | 11 +- vinvoor/src/navbar/NavBarUserMenu.tsx | 193 ++-- vinvoor/src/overview/Overview.tsx | 141 +-- vinvoor/src/overview/checkin/CheckIn.tsx | 49 +- vinvoor/src/overview/days/Days.tsx | 17 +- vinvoor/src/overview/heatmap/Day.tsx | 177 ++-- vinvoor/src/overview/heatmap/Heatmap.tsx | 14 +- vinvoor/src/overview/heatmap/LabelsMonth.tsx | 13 +- vinvoor/src/overview/heatmap/Rect.tsx | 4 +- vinvoor/src/overview/heatmap/utils.ts | 56 +- vinvoor/src/overview/streak/Streak.tsx | 105 ++- .../src/providers/CustomSnackbarProvider.tsx | 6 +- vinvoor/src/providers/ThemeProvider.tsx | 9 +- vinvoor/src/scans/Scans.tsx | 8 +- vinvoor/src/scans/ScansTableBody.tsx | 17 +- vinvoor/src/scans/ScansTableHead.tsx | 4 +- vinvoor/src/settings/Settings.tsx | 22 +- vinvoor/src/settings/SettingsOverview.tsx | 4 +- vinvoor/src/settings/admin/Admin.tsx | 2 +- vinvoor/src/settings/admin/days/Days.tsx | 6 +- vinvoor/src/settings/admin/days/DaysAdd.tsx | 10 +- vinvoor/src/settings/admin/days/DaysTable.tsx | 50 +- .../src/settings/admin/days/DaysTableBody.tsx | 8 +- .../src/settings/admin/days/DaysTableHead.tsx | 10 +- .../settings/admin/days/DaysTableToolbar.tsx | 32 +- .../src/settings/admin/seasons/Seasons.tsx | 8 +- .../src/settings/admin/seasons/SeasonsAdd.tsx | 20 +- .../settings/admin/seasons/SeasonsTable.tsx | 24 +- .../admin/seasons/SeasonsTableBody.tsx | 13 +- .../admin/seasons/SeasonsTableHead.tsx | 10 +- vinvoor/src/themes/theme.ts | 5 +- vinvoor/src/types/cards.ts | 36 +- vinvoor/src/types/days.ts | 7 +- vinvoor/src/types/leaderboard.ts | 9 +- vinvoor/src/types/scans.ts | 17 +- vinvoor/src/types/seasons.ts | 7 +- vinvoor/src/types/settings.ts | 8 +- vinvoor/src/types/version.ts | 8 +- vinvoor/src/util/fetch.ts | 62 +- vinvoor/src/util/util.ts | 50 +- vinvoor/tsconfig.json | 32 +- vinvoor/tsconfig.node.json | 4 +- 88 files changed, 1898 insertions(+), 971 deletions(-) create mode 100755 .githooks/pre-commit delete mode 100644 .githooks/vinvoor_format_lint rename .github/workflows/{vinvoor_format_lint.yml => vinvoor.yml} (54%) delete mode 100644 vinvoor/.eslintrc.cjs delete mode 100644 vinvoor/.prettierrc diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..0284a7a --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# some (*)nix distros dont have /bin/bash + +echo "Frontend linting" +(cd vinvoor && pnpm --silent run precommit:lint) +if [ $? -ne 0 ]; then + echo "Frontend linting failed. Please fix the errors before committing." + exit 1 +fi + +echo "Frontend typecheck" +(cd vinvoor && pnpm --silent run precommit:typecheck) +if [ $? -ne 0 ]; then + echo "Frontend type checking failed. Please fix the errors before committing." + exit 1 +fi diff --git a/.githooks/vinvoor_format_lint b/.githooks/vinvoor_format_lint deleted file mode 100644 index 41ecf78..0000000 --- a/.githooks/vinvoor_format_lint +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# some (*)nix distros dont have /bin/bash - -cd vinvoor/ - -pnpm run format - -if [ $? -ne 0 ]; then - echo "Error: code improperly formatted!" - exit 1 -fi - -pnpm run lint - -if [ $? -ne 0 ]; then - echo "Error: code improperly linted!" - exit 1 -fi - -exit 0 diff --git a/.github/workflows/vinvoor_format_lint.yml b/.github/workflows/vinvoor.yml similarity index 54% rename from .github/workflows/vinvoor_format_lint.yml rename to .github/workflows/vinvoor.yml index 3195e8f..b740f45 100644 --- a/.github/workflows/vinvoor_format_lint.yml +++ b/.github/workflows/vinvoor.yml @@ -1,16 +1,10 @@ -name: Format and linting +name: Vinvoor Format, Lint & Typecheck on: push: branches: - main - paths: - - .github/workflows/vinvoor_format_lint.yml - - 'vinvoor/**' pull_request: - paths: - - .github/workflows/vinvoor_format_lint.yml - - 'vinvoor/**' jobs: format-and-lint: @@ -20,10 +14,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Node.js + - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: 20.15.1 + node-version: 22.8.0 - name: Install pnpm run: npm install -g pnpm @@ -32,10 +26,10 @@ jobs: run: pnpm install working-directory: vinvoor/ - - name: Run formatter - run: pnpm prettier --check . + - name: Run format & lint + run: pnpm eslint . working-directory: vinvoor/ - - name: Run Linter - run: pnpm eslint . --max-warnings=0 + - name: Run typecheck + run: pnpm tsc --noEmit working-directory: vinvoor/ diff --git a/vinvoor/.eslintrc.cjs b/vinvoor/.eslintrc.cjs deleted file mode 100644 index 6e8698b..0000000 --- a/vinvoor/.eslintrc.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react-hooks/recommended", - ], - ignorePatterns: ["dist", ".eslintrc.cjs"], - parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], - rules: { - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], - }, -}; diff --git a/vinvoor/.prettierrc b/vinvoor/.prettierrc deleted file mode 100644 index aa7dddc..0000000 --- a/vinvoor/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tabWidth": 2, - "arrowParens": "avoid" -} diff --git a/vinvoor/eslint.config.mjs b/vinvoor/eslint.config.mjs index aadd73b..82a73a5 100644 --- a/vinvoor/eslint.config.mjs +++ b/vinvoor/eslint.config.mjs @@ -1,28 +1,24 @@ -import { includeIgnoreFile } from "@eslint/compat"; -import eslint from "@eslint/js"; -import eslintConfigPrettier from "eslint-config-prettier"; -import path, { dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import tseslint from "typescript-eslint"; +import antfu from "@antfu/eslint-config"; -const __dirname = dirname(fileURLToPath(import.meta.url)); -const gitignorePath = path.resolve(__dirname, ".gitignore"); - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommendedTypeChecked, - ...tseslint.configs.stylisticTypeChecked, - includeIgnoreFile(gitignorePath), +export default antfu( { - languageOptions: { - parserOptions: { - projectService: true, - tsconfigRootDir: __dirname, - }, + stylistic: { + quotes: "double", + semi: true, + }, + + react: true, + + typescript: { + tsconfigPath: "./tsconfig.json", + }, + + formatters: true, + + rules: { + "react-hooks/exhaustive-deps": "off", + "ts/switch-exhaustiveness-check": "off", + "ts/strict-boolean-expressions": "off", }, }, - { - ignores: ["eslint.config.mjs", ".eslintrc.cjs"], - }, - eslintConfigPrettier, ); diff --git a/vinvoor/package.json b/vinvoor/package.json index 7380f4c..edac4b6 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -1,8 +1,8 @@ { "name": "vinvoor", - "private": true, - "version": "0.0.0", "type": "module", + "version": "0.0.0", + "private": true, "scripts": { "dev": "vite", "host": "vite --host", @@ -42,8 +42,6 @@ "@types/node": "^22.9.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react-swc": "^3.7.1", "eslint": "^9.15.0", "eslint-config-prettier": "^9.1.0", diff --git a/vinvoor/pnpm-lock.yaml b/vinvoor/pnpm-lock.yaml index 8784dcb..fdd5c01 100644 --- a/vinvoor/pnpm-lock.yaml +++ b/vinvoor/pnpm-lock.yaml @@ -1120,6 +1120,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1240,7 +1244,162 @@ packages: resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + eslint: '>=6.0.0' + + eslint-compat-utils@0.6.3: + resolution: {integrity: sha512-9IDdksh5pUYP2ZLi7mOdROxVjLY8gY2qKxprmrJ/5Dyqud7M/IFKxF3o0VLlRhITm1pK6Fk7NiBxE39M/VlUcw==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-config-flat-gitignore@0.3.0: + resolution: {integrity: sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==} + peerDependencies: + eslint: ^9.5.0 + + eslint-flat-config-utils@0.4.0: + resolution: {integrity: sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==} + + eslint-formatting-reporter@0.0.0: + resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==} + peerDependencies: + eslint: '>=8.40.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-json-compat-utils@0.2.1: + resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} + engines: {node: '>=12'} + peerDependencies: + '@eslint/json': '*' + eslint: '*' + jsonc-eslint-parser: ^2.4.0 + peerDependenciesMeta: + '@eslint/json': + optional: true + + eslint-merge-processors@0.1.0: + resolution: {integrity: sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==} + peerDependencies: + eslint: '*' + + eslint-parser-plain@0.1.0: + resolution: {integrity: sha512-oOeA6FWU0UJT/Rxc3XF5Cq0nbIZbylm7j8+plqq0CZoE6m4u32OXJrR+9iy4srGMmF6v6pmgvP1zPxSRIGh3sg==} + + eslint-plugin-antfu@2.7.0: + resolution: {integrity: sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==} + peerDependencies: + eslint: '*' + + eslint-plugin-command@0.2.6: + resolution: {integrity: sha512-T0bHZ1oblW1xUHUVoBKZJR2osSNNGkfZuK4iqboNwuNS/M7tdp3pmURaJtTi/XDzitxaQ02lvOdFH0mUd5QLvQ==} + peerDependencies: + eslint: '*' + + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + + eslint-plugin-format@0.1.2: + resolution: {integrity: sha512-ZrcO3aiumgJ6ENAv65IWkPjtW77ML/5mp0YrRK0jdvvaZJb+4kKWbaQTMr/XbJo6CtELRmCApAziEKh7L2NbdQ==} + peerDependencies: + eslint: ^8.40.0 || ^9.0.0 + + eslint-plugin-import-x@4.4.2: + resolution: {integrity: sha512-mDRXPSLQ0UQZQw91QdG4/qZT6hgeW2MJTczAbgPseUZuPEtIjjdPOolXroRkulnOn3fzj6gNgvk+wchMJiHElg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + eslint-plugin-jsdoc@50.5.0: + resolution: {integrity: sha512-xTkshfZrUbiSHXBwZ/9d5ulZ2OcHXxSvm/NPo494H/hadLRJwOq5PMV0EUpMqsb9V+kQo+9BAgi6Z7aJtdBp2A==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-jsonc@2.18.2: + resolution: {integrity: sha512-SDhJiSsWt3nItl/UuIv+ti4g3m4gpGkmnUJS9UWR3TrpyNsIcnJoBRD7Kof6cM4Rk3L0wrmY5Tm3z7ZPjR2uGg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-n@17.13.2: + resolution: {integrity: sha512-MhBAKkT01h8cOXcTBTlpuR7bxH5OBUNpUXefsvwSVEy46cY4m/Kzr2osUCQvA3zJFD6KuCeNNDv0+HDuWk/OcA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.23.0' + + eslint-plugin-no-only-tests@3.3.0: + resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} + engines: {node: '>=5.0.0'} + + eslint-plugin-perfectionist@3.9.1: + resolution: {integrity: sha512-9WRzf6XaAxF4Oi5t/3TqKP5zUjERhasHmLFHin2Yw6ZAp/EP/EVA2dr3BhQrrHWCm5SzTMZf0FcjDnBkO2xFkA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + astro-eslint-parser: ^1.0.2 + eslint: '>=8.0.0' + svelte: '>=3.0.0' + svelte-eslint-parser: ^0.41.1 + vue-eslint-parser: '>=9.0.0' + peerDependenciesMeta: + astro-eslint-parser: + optional: true + svelte: + optional: true + svelte-eslint-parser: + optional: true + vue-eslint-parser: + optional: true + + eslint-plugin-react-debug@1.16.1: + resolution: {integrity: sha512-AijumibZ+3hBYCGBEeD3GQse5TPnq9z6bX0qfsFwCwWjkW+siL2EEGvaxT7UZp2mcFMvoRJT3E4Jsemn6g0AGw==} + engines: {bun: '>=1.0.15', node: '>=18.18.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ^4.9.5 || ^5.3.3 + peerDependenciesMeta: + typescript: + optional: true + + eslint-plugin-react-dom@1.16.1: + resolution: {integrity: sha512-qJFfCR2Rofd5/V9/8EE4sg6a829HcI07DeK7qqTosYRPBYkwbfUUjvizzlTxneMAcPQuFfPZa1UMDTaejKStyg==} + engines: {bun: '>=1.0.15', node: '>=18.18.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ^4.9.5 || ^5.3.3 + peerDependenciesMeta: + typescript: + optional: true + + eslint-plugin-react-hooks-extra@1.16.1: + resolution: {integrity: sha512-OJ4RJZ7n25XnF6+NaFC9dzrec2C+/o4zb4Brs+v6fVVbvQQZirgWamKZMOJo+I1HsHdOULtBo1uwopLfnVBihQ==} + engines: {bun: '>=1.0.15', node: '>=18.18.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ^4.9.5 || ^5.3.3 + peerDependenciesMeta: + typescript: + optional: true + + eslint-plugin-react-hooks@5.1.0-beta-26f2496093-20240514: + resolution: {integrity: sha512-nCZD93/KYY5hNAWGhfvvrEXvLFIXJCMu2St7ciHeiWUp/lnS2RVgWawp2kNQamr9Y23C9lUA03TmDRNgbm05vg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-naming-convention@1.16.1: + resolution: {integrity: sha512-qyZ6YW82vLHHQEboc0LhE+9Uga2koCtwEV0XYEWxq3DI3Wg1SlwsfchPYQc7skRh2c/Jh9YG2gzRmNXG4Ul2Ww==} + engines: {bun: '>=1.0.15', node: '>=18.18.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ^4.9.5 || ^5.3.3 + peerDependenciesMeta: + typescript: + optional: true eslint-plugin-react-refresh@0.4.14: resolution: {integrity: sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==} @@ -1367,6 +1526,9 @@ packages: peerDependencies: csstype: ^3.0.10 + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1448,6 +1610,10 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-eslint-parser@2.4.0: + resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1482,6 +1648,39 @@ packages: react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 + mdast-util-find-and-replace@3.0.1: + resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.0.0: + resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.0.0: + resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdi-material-ui@7.9.2: resolution: {integrity: sha512-MZ5zxN7W6yqtHwNYtV/Ezt6nQtMPsA3UtHcNpHwSDbFdvWgspTKyxh4gS63G8e49qyJAPdq0JMjxV3SEnXu4yQ==} peerDependencies: @@ -1511,6 +1710,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -1527,6 +1729,9 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1791,6 +1996,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -1839,6 +2047,12 @@ packages: terser: optional: true + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2730,7 +2944,6 @@ snapshots: eslint: 9.15.0 transitivePeerDependencies: - supports-color - - typescript '@typescript-eslint/utils@8.15.0(eslint@9.15.0)(typescript@5.6.3)': dependencies: @@ -2776,6 +2989,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -2790,9 +3005,9 @@ snapshots: svg.resize.js: 1.4.3 svg.select.js: 3.0.1 - argparse@2.0.1: {} + are-docs-informative@0.0.2: {} - array-union@2.1.0: {} + argparse@2.0.1: {} babel-plugin-macros@3.1.0: dependencies: @@ -2802,6 +3017,10 @@ snapshots: balanced-match@1.0.2: {} + birecord@0.1.1: {} + + boolbase@1.0.0: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -2822,6 +3041,8 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) + builtin-modules@3.3.0: {} + callsites@3.1.0: {} camelcase@6.3.0: {} @@ -2833,8 +3054,22 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + character-entities@2.0.2: {} + + ci-info@4.1.0: {} + classnames@2.5.1: {} + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@1.2.1: {} clsx@2.1.1: {} @@ -2845,12 +3080,20 @@ snapshots: color-name@1.1.4: {} + comment-parser@1.4.1: {} + concat-map@0.0.1: {} + confbox@0.1.8: {} + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} + core-js-compat@3.39.0: + dependencies: + browserslist: 4.24.2 + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -2874,19 +3117,35 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cssesc@3.0.0: {} + csstype@3.1.3: {} dayjs@1.11.13: {} + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.3.7: dependencies: ms: 2.1.3 + decode-named-character-reference@1.0.2: + dependencies: + character-entities: 2.0.2 + deep-is@0.1.4: {} - dir-glob@3.0.1: + dequal@2.0.3: {} + + devlop@1.1.0: dependencies: - path-type: 4.0.0 + dequal: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 dom-helpers@5.2.1: dependencies: @@ -2906,6 +3165,8 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-module-lexer@1.5.4: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -2934,6 +3195,8 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@9.1.0(eslint@9.15.0): @@ -3018,6 +3281,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3044,6 +3309,13 @@ snapshots: find-root@1.1.0: {} + find-up-simple@1.0.0: {} + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3063,6 +3335,12 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3073,21 +3351,20 @@ snapshots: globals@11.12.0: {} + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + globals@14.0.0: {} - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 + globals@15.12.0: {} goober@2.1.16(csstype@3.1.3): dependencies: csstype: 3.1.3 + graceful-fs@4.2.11: {} + graphemer@1.4.0: {} has-flag@4.0.0: {} @@ -3100,6 +3377,8 @@ snapshots: dependencies: react-is: 16.13.1 + hosted-git-info@2.8.9: {} + ignore@5.3.2: {} import-fresh@3.3.0: @@ -3109,18 +3388,36 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + is-arrayish@0.2.1: {} + is-builtin-module@3.2.1: + dependencies: + builtin-modules: 3.3.0 + is-core-module@2.15.1: dependencies: hasown: 2.0.2 is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-immutable-type@5.0.0(eslint@9.15.0)(typescript@5.6.3): + dependencies: + '@typescript-eslint/type-utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3) + eslint: 9.15.0 + ts-api-utils: 1.4.0(typescript@5.6.3) + ts-declaration-location: 1.0.4(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + is-number@7.0.0: {} isexe@2.0.0: {} @@ -3133,6 +3430,10 @@ snapshots: dependencies: argparse: 2.0.1 + jsdoc-type-pratt-parser@4.1.0: {} + + jsesc@0.5.0: {} + jsesc@3.0.2: {} json-buffer@3.0.1: {} @@ -3145,6 +3446,13 @@ snapshots: json5@2.2.3: {} + jsonc-eslint-parser@2.4.0: + dependencies: + acorn: 8.14.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.6.3 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3156,12 +3464,25 @@ snapshots: lines-and-columns@1.2.4: {} + local-pkg@0.5.1: + dependencies: + mlly: 1.7.3 + pkg-types: 1.2.1 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash.merge@4.6.2: {} + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -3174,12 +3495,120 @@ snapshots: dependencies: yallist: 3.1.1 + magic-string@0.30.13: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + markdown-table@3.0.4: {} + material-ui-confirm@3.0.16(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@mui/material': 5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + mdast-util-find-and-replace@3.0.1: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.1 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdi-material-ui@7.9.2(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@mui/material': 5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3187,11 +3616,208 @@ snapshots: merge2@1.4.1: {} + micromark-core-commonmark@2.0.2: + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.0.2 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-table@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.1 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.0 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.1 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.1 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.0.2: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.1: {} + + micromark@4.0.1: + dependencies: + '@types/debug': 4.1.12 + debug: 4.3.7 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.0.2 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 + min-indent@1.0.1: {} + + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -3200,10 +3826,19 @@ snapshots: dependencies: brace-expansion: 2.0.1 + mlly@1.7.3: + dependencies: + acorn: 8.14.0 + pathe: 1.1.2 + pkg-types: 1.2.1 + ufo: 1.5.4 + ms@2.1.3: {} nanoid@3.3.7: {} + natural-compare-lite@1.4.0: {} + natural-compare@1.4.0: {} no-case@3.0.4: @@ -3213,6 +3848,13 @@ snapshots: node-releases@2.0.18: {} + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.8 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + notistack@3.0.1(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: clsx: 1.2.1 @@ -3222,6 +3864,10 @@ snapshots: transitivePeerDependencies: - csstype + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + object-assign@4.1.1: {} optionator@0.9.4: @@ -3233,18 +3879,37 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + + package-manager-detector@0.2.4: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-gitignore@2.0.0: {} + + parse-imports@2.2.1: + dependencies: + es-module-lexer: 1.5.4 + slashes: 3.0.12 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.26.2 @@ -3260,6 +3925,8 @@ snapshots: path-type@4.0.0: {} + pathe@1.1.2: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3274,6 +3941,10 @@ snapshots: prelude-ls@1.2.1: {} + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + prettier@3.3.3: {} prop-types@15.8.1: @@ -3340,10 +4011,42 @@ snapshots: dependencies: loose-envify: 1.4.0 + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + regenerator-runtime@0.14.1: {} + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regjsparser@0.10.0: + dependencies: + jsesc: 0.5.0 + + require-directory@2.1.1: {} + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.8: dependencies: is-core-module: 2.15.1 @@ -3384,6 +4087,14 @@ snapshots: dependencies: loose-envify: 1.4.0 + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + + semver@5.7.2: {} + semver@6.3.1: {} semver@7.6.3: {} @@ -3405,6 +4116,43 @@ snapshots: source-map@0.5.7: {} + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.20 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + + spdx-license-ids@3.0.20: {} + + stable-hash@0.0.4: {} + + string-ts@2.2.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} stylis@4.2.0: {} @@ -3475,8 +4223,29 @@ snapshots: typescript@5.6.3: {} + ufo@1.5.4: {} + undici-types@6.19.8: {} + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: browserslist: 4.24.2 @@ -3507,14 +4276,59 @@ snapshots: '@types/node': 22.9.0 fsevents: 2.3.3 + vue-eslint-parser@9.4.3(eslint@9.15.0): + dependencies: + debug: 4.3.7 + eslint: 9.15.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + which@2.0.2: dependencies: isexe: 2.0.0 word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + xml-name-validator@4.0.0: {} + + y18n@5.0.8: {} + yallist@3.1.1: {} + yaml-eslint-parser@1.2.3: + dependencies: + eslint-visitor-keys: 3.4.3 + lodash: 4.17.21 + yaml: 2.6.0 + yaml@1.10.2: {} + yaml@2.6.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index 27477c6..d667b00 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -6,11 +6,11 @@ import { Footer } from "./footer/Footer"; import { useUser } from "./hooks/useUser"; import { NavBar } from "./navbar/NavBar"; import { Overview } from "./overview/Overview"; -import "./themes/background.css"; import { randomInt } from "./util/util"; import { WelcomePage } from "./WelcomePage"; +import "./themes/background.css"; -export const App = () => { +export function App() { const userQuery = useUser(); const outlet = useOutlet(); @@ -34,18 +34,22 @@ export const App = () => { }} > - {Object.keys(userQuery.data ?? {}).length > 0 ? ( - outlet !== null ? ( - - ) : ( - - ) - ) : ( - <> - - - - )} + {Object.keys(userQuery.data ?? {}).length > 0 + ? ( + outlet !== null + ? ( + + ) + : ( + + ) + ) + : ( + <> + + + + )} @@ -53,4 +57,4 @@ export const App = () => { ); -}; +} diff --git a/vinvoor/src/WelcomePage.tsx b/vinvoor/src/WelcomePage.tsx index 2113ed7..52efd46 100644 --- a/vinvoor/src/WelcomePage.tsx +++ b/vinvoor/src/WelcomePage.tsx @@ -1,8 +1,8 @@ import { GitHub } from "@mui/icons-material"; import { Box, Button, Typography } from "@mui/material"; import { ShakerOutline } from "mdi-material-ui"; -import { TypographyG } from "./components/TypographyG"; import { Login } from "./auth/Login"; +import { TypographyG } from "./components/TypographyG"; declare module "@mui/material/Button" { interface ButtonPropsColorOverrides { @@ -10,7 +10,7 @@ declare module "@mui/material/Button" { } } -export const WelcomePage = () => { +export function WelcomePage() { const handleClick = () => { window.location.replace("https://github.com/ZeusWPI/ZeSS"); }; @@ -46,4 +46,4 @@ export const WelcomePage = () => { ); -}; +} diff --git a/vinvoor/src/auth/Login.tsx b/vinvoor/src/auth/Login.tsx index d8a48e9..591ea76 100644 --- a/vinvoor/src/auth/Login.tsx +++ b/vinvoor/src/auth/Login.tsx @@ -1,7 +1,8 @@ -import { Button, ButtonProps } from "@mui/material"; -import { FC } from "react"; +import type { ButtonProps } from "@mui/material"; +import type { FC } from "react"; +import { Button } from "@mui/material"; -export const Login: FC = props => { +export const Login: FC = (props) => { const url = import.meta.env.VITE_BACKEND_URL as string; const handleClick = () => { diff --git a/vinvoor/src/auth/Logout.tsx b/vinvoor/src/auth/Logout.tsx index c5fb8c3..c3a3b3c 100644 --- a/vinvoor/src/auth/Logout.tsx +++ b/vinvoor/src/auth/Logout.tsx @@ -1,8 +1,9 @@ -import { Button, ButtonProps } from "@mui/material"; -import { FC } from "react"; +import type { ButtonProps } from "@mui/material"; +import type { FC } from "react"; +import { Button } from "@mui/material"; import { useLogout } from "../hooks/useUser"; -export const Logout: FC = props => { +export const Logout: FC = (props) => { const logout = useLogout(); const handleClick = () => { logout.mutate(); diff --git a/vinvoor/src/cards/Cards.tsx b/vinvoor/src/cards/Cards.tsx index f46cdf6..8083d4c 100644 --- a/vinvoor/src/cards/Cards.tsx +++ b/vinvoor/src/cards/Cards.tsx @@ -3,7 +3,7 @@ import { useCards } from "../hooks/useCard"; import { CardsEmpty } from "./CardsEmpty"; import { CardsTable } from "./CardsTable"; -export const Cards = () => { +export function Cards() { const cardsQuery = useCards(); return ( @@ -11,4 +11,4 @@ export const Cards = () => { {cardsQuery.data?.length ? : } ); -}; +} diff --git a/vinvoor/src/cards/CardsAdd.tsx b/vinvoor/src/cards/CardsAdd.tsx index 36826c2..b070df3 100644 --- a/vinvoor/src/cards/CardsAdd.tsx +++ b/vinvoor/src/cards/CardsAdd.tsx @@ -1,24 +1,28 @@ +import type { + CardGetRegisterResponse, + CardGetRegisterResponseJSON, + CardPostResponse, + CardPostResponseJSON, +} from "../types/cards"; +import type { Optional } from "../types/general"; +import type { + CircularTimeProgressProps, +} from "./CircularTimeProgress"; import { Add } from "@mui/icons-material"; import { Button, Typography } from "@mui/material"; import { useConfirm } from "material-ui-confirm"; import { useSnackbar } from "notistack"; import { useEffect, useState } from "react"; +import { useCards } from "../hooks/useCard"; import { - CardGetRegisterResponse, - CardGetRegisterResponseJSON, - CardPostResponse, - CardPostResponseJSON, convertCardGetRegisterResponseJSON, convertCardPostResponseJSON, } from "../types/cards"; -import { Optional } from "../types/general"; import { getApi, isResponseNot200Error, postApi } from "../util/fetch"; import { randomInt } from "../util/util"; import { CircularTimeProgress, - CircularTimeProgressProps, } from "./CircularTimeProgress"; -import { useCards } from "../hooks/useCard"; const CHECK_INTERVAL = 1000; const REGISTER_TIME = 60000; @@ -37,19 +41,19 @@ const confirmContent = ` const requestSuccess = "Register your card by holding it to vinscant"; const requestYou = "You are already registering a card!"; -const requestOther = - "Failed to start the card registering process because another user is already registering a card. Please try again later."; -const requestFail = - "Failed to start the card registration process. Please try again later or contact a sysadmin"; +const requestOther + = "Failed to start the card registering process because another user is already registering a card. Please try again later."; +const requestFail + = "Failed to start the card registration process. Please try again later or contact a sysadmin"; const registerSucces = "Card registered successfully"; const registerFail = "Failed to register card"; -export const CardsAdd = () => { +export function CardsAdd() { const { refetch } = useCards(); const [registering, setRegistering] = useState(false); - const [progressProps, setProgressProps] = - useState(defaultProgressProps); + const [progressProps, setProgressProps] + = useState(defaultProgressProps); const confirm = useConfirm(); const { enqueueSnackbar, closeSnackbar } = useSnackbar(); @@ -78,7 +82,7 @@ export const CardsAdd = () => { REGISTER_ENDPOINT, convertCardGetRegisterResponseJSON, ) - .then(async response => { + .then(async (response) => { let started = false; if (!response.registering && start) { await postApi( @@ -87,25 +91,31 @@ export const CardsAdd = () => { convertCardPostResponseJSON, ) .then(() => (started = true)) - .catch(error => { + .catch((error) => { if (isResponseNot200Error(error)) { void error.response .json() .then((response: CardPostResponse) => { - if (response.isCurrentUser) + if (response.isCurrentUser) { enqueueSnackbar(requestYou, { variant: "warning", }); - else + } + else { enqueueSnackbar(requestOther, { variant: "error", }); + } }); - } else throw new Error(error as string); + } + else { + throw new Error(error as string); + } }); } - if (response.registering && response.isCurrentUser) started = true; + if (response.registering && response.isCurrentUser) + started = true; if (started) { setRegistering(true); @@ -121,9 +131,9 @@ export const CardsAdd = () => { } void checkCardsChange() - .then(scanned => { + .then((scanned) => { setRegistering(false); - if (id) { + if (id !== undefined) { closeSnackbar(id); if (scanned) { @@ -131,10 +141,12 @@ export const CardsAdd = () => { variant: "success", }); void refetch(); - } else + } + else { enqueueSnackbar(registerFail, { variant: "error", }); + } } }) .finally(() => setProgressProps(defaultProgressProps)); @@ -170,4 +182,4 @@ export const CardsAdd = () => { Register new card ); -}; +} diff --git a/vinvoor/src/cards/CardsDelete.tsx b/vinvoor/src/cards/CardsDelete.tsx index e16520a..0ee8ac5 100644 --- a/vinvoor/src/cards/CardsDelete.tsx +++ b/vinvoor/src/cards/CardsDelete.tsx @@ -1,8 +1,8 @@ +import type { FC } from "react"; import DeleteIcon from "@mui/icons-material/Delete"; import { IconButton, Link, Tooltip, Typography } from "@mui/material"; import { useConfirm } from "material-ui-confirm"; import { useSnackbar } from "notistack"; -import { FC } from "react"; interface CardDeleteProps { selected: readonly string[]; @@ -18,16 +18,22 @@ export const CardsDelete: FC = ({ selected }) => { const title = `Delete card${numSelected > 1 ? "s" : ""}`; const content = ( - Are you sure you want to delete {numSelected} card - {numSelected > 1 ? "s" : ""}? Unfortunately, this feature isn't available - yet. Let's convince Hannes to add this feature by signing this{" "} + Are you sure you want to delete + {" "} + {numSelected} + {" "} + card + {numSelected > 1 ? "s" : ""} + ? Unfortunately, this feature isn't available + yet. Let's convince Hannes to add this feature by signing this + {" "} petition! ); const handleClick = () => { void confirm({ - title: title, + title, description: content, confirmationText: "Delete", }).then(() => enqueueSnackbar(deletePressed, { variant: "error" })); diff --git a/vinvoor/src/cards/CardsEmpty.tsx b/vinvoor/src/cards/CardsEmpty.tsx index d936b52..6048cac 100644 --- a/vinvoor/src/cards/CardsEmpty.tsx +++ b/vinvoor/src/cards/CardsEmpty.tsx @@ -2,7 +2,7 @@ import { Paper } from "@mui/material"; import { TypographyG } from "../components/TypographyG"; import { CardsAdd } from "./CardsAdd"; -export const CardsEmpty = () => { +export function CardsEmpty() { return ( { ); -}; +} diff --git a/vinvoor/src/cards/CardsTable.tsx b/vinvoor/src/cards/CardsTable.tsx index 8f6c158..26efe70 100644 --- a/vinvoor/src/cards/CardsTable.tsx +++ b/vinvoor/src/cards/CardsTable.tsx @@ -1,37 +1,34 @@ +import type { ChangeEvent, MouseEvent } from "react"; +import type { Card } from "../types/cards"; +import type { TableOrder } from "../types/general"; import { Paper, Table, TableContainer, TablePagination } from "@mui/material"; -import { ChangeEvent, MouseEvent, useMemo, useState } from "react"; -import { Card } from "../types/cards"; -import { TableOrder } from "../types/general"; +import { useMemo, useState } from "react"; +import { useCards } from "../hooks/useCard"; import { CardsTableBody } from "./CardsTableBody"; import { CardsTableHead } from "./CardsTableHead"; import { CardsTableToolbar } from "./CardsTableToolbar"; -import { useCards } from "../hooks/useCard"; const rowsPerPageOptions = [10, 25, 50]; -const descendingComparator = (a: T, b: T, orderBy: keyof T) => { - if (b[orderBy] < a[orderBy]) return -1; - if (b[orderBy] > a[orderBy]) return 1; +function descendingComparator(a: T, b: T, orderBy: keyof T) { + if (b[orderBy] < a[orderBy]) + return -1; + if (b[orderBy] > a[orderBy]) + return 1; return 0; -}; +} -const getComparator = ( - order: TableOrder, - orderBy: Key, -): (( +function getComparator(order: TableOrder, orderBy: Key): (( a: Record, b: Record, -) => number) => { +) => number) { return order === "desc" ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy); -}; +} -const stableSort = ( - array: readonly T[], - comparator: (a: T, b: T) => number, -) => { +function stableSort(array: readonly T[], comparator: (a: T, b: T) => number) { const stabilized = array.map((el, index) => [el, index] as [T, number]); stabilized.sort((a, b) => { const order = comparator(a[0], b[0]); @@ -41,11 +38,10 @@ const stableSort = ( return a[1] - b[1]; }); return stabilized.map(el => el[0]); -}; +} -export const CardsTable = () => { +export function CardsTable() { const { data: cards } = useCards(); - if (!cards) return null; // Can never happen const [order, setOrder] = useState("asc"); const [orderBy, setOrderBy] = useState("serial"); @@ -53,6 +49,18 @@ export const CardsTable = () => { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); + const visibleRows = useMemo( + () => + stableSort(cards ?? [], getComparator(order, orderBy)).slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, + ), + [cards, order, orderBy, page, rowsPerPage], + ); + + if (!cards) + return null; // Can never happen + const handleRequestSort = ( _: MouseEvent, property: keyof Card, @@ -106,23 +114,14 @@ export const CardsTable = () => { ) => setPage(newPage); const handleChangeRowsPerPage = (event: ChangeEvent) => { - setRowsPerPage(parseInt(event.target.value, 10)); + setRowsPerPage(Number.parseInt(event.target.value, 10)); setPage(0); }; const isSelected = (serial: string) => selected.includes(serial); - const emptyRows = - page > 0 ? Math.max(0, (1 + page) * rowsPerPage - cards.length) : 0; - - const visibleRows = useMemo( - () => - stableSort(cards, getComparator(order, orderBy)).slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage, - ), - [cards, order, orderBy, page, rowsPerPage], - ); + const emptyRows + = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - cards.length) : 0; return ( @@ -156,4 +155,4 @@ export const CardsTable = () => { /> ); -}; +} diff --git a/vinvoor/src/cards/CardsTableBody.tsx b/vinvoor/src/cards/CardsTableBody.tsx index 1dfd2e4..2440e8b 100644 --- a/vinvoor/src/cards/CardsTableBody.tsx +++ b/vinvoor/src/cards/CardsTableBody.tsx @@ -1,3 +1,5 @@ +import type { ChangeEvent, FC, MouseEvent } from "react"; +import type { Card } from "../types/cards"; import { EditOutlined } from "@mui/icons-material"; import { Checkbox, @@ -10,9 +12,8 @@ import { } from "@mui/material"; import { useConfirm } from "material-ui-confirm"; import { useSnackbar } from "notistack"; -import { ChangeEvent, FC, MouseEvent } from "react"; -import { Card, cardsHeadCells } from "../types/cards"; import { useCards, usePatchCards } from "../hooks/useCard"; +import { cardsHeadCells } from "../types/cards"; interface CardsTableBodyProps { rows: readonly Card[]; @@ -47,9 +48,9 @@ export const CardsTableBody: FC = ({ variant="standard" defaultValue={name} onChange={(event: ChangeEvent) => - (newName = event.target.value) - } - > + (newName = event.target.value)} + > + ), confirmationText: "Save", }) @@ -60,7 +61,7 @@ export const CardsTableBody: FC = ({ } patchCard.mutate( - { id: id, newName: newName }, + { id, newName }, { onSuccess: () => { enqueueSnackbar(nameSaveSuccess, { @@ -86,7 +87,7 @@ export const CardsTableBody: FC = ({ return ( - {rows.map(row => { + {rows.map((row) => { const isSelected = isRowSelected(row.serial); return ( diff --git a/vinvoor/src/cards/CardsTableHead.tsx b/vinvoor/src/cards/CardsTableHead.tsx index 7a6fb8d..2eed412 100644 --- a/vinvoor/src/cards/CardsTableHead.tsx +++ b/vinvoor/src/cards/CardsTableHead.tsx @@ -1,3 +1,6 @@ +import type { ChangeEvent, FC, MouseEvent } from "react"; +import type { Card } from "../types/cards"; +import type { TableOrder } from "../types/general"; import { Checkbox, TableCell, @@ -6,9 +9,7 @@ import { TableSortLabel, Typography, } from "@mui/material"; -import { ChangeEvent, FC, MouseEvent } from "react"; -import { Card, cardsHeadCells } from "../types/cards"; -import { TableOrder } from "../types/general"; +import { cardsHeadCells } from "../types/cards"; interface CardTableHeadProps { numSelected: number; @@ -30,8 +31,8 @@ export const CardsTableHead: FC = ({ orderBy, rowCount, }) => { - const createSortHandler = - (property: keyof Card) => (event: MouseEvent) => + const createSortHandler + = (property: keyof Card) => (event: MouseEvent) => onRequestSort(event, property); return ( diff --git a/vinvoor/src/cards/CardsTableToolbar.tsx b/vinvoor/src/cards/CardsTableToolbar.tsx index 85413a3..ab98f5c 100644 --- a/vinvoor/src/cards/CardsTableToolbar.tsx +++ b/vinvoor/src/cards/CardsTableToolbar.tsx @@ -1,6 +1,6 @@ +import type { FC } from "react"; import { Toolbar, Typography } from "@mui/material"; import { alpha } from "@mui/material/styles"; -import { FC } from "react"; import { CardsAdd } from "./CardsAdd"; import { CardsDelete } from "./CardsDelete"; @@ -24,25 +24,29 @@ export const CardsTableToolbar: FC = ({ selected }) => { }), }} > - {numSelected > 0 ? ( - <> - - {numSelected} selected - - - - ) : ( - <> - - Cards - - - - )} + {numSelected > 0 + ? ( + <> + + {numSelected} + {" "} + selected + + + + ) + : ( + <> + + Cards + + + + )} ); }; diff --git a/vinvoor/src/cards/CircularTimeProgress.tsx b/vinvoor/src/cards/CircularTimeProgress.tsx index 6f274cc..fe0e39c 100644 --- a/vinvoor/src/cards/CircularTimeProgress.tsx +++ b/vinvoor/src/cards/CircularTimeProgress.tsx @@ -1,5 +1,5 @@ +import type { FC } from "react"; import { Box, CircularProgress, Typography } from "@mui/material"; -import { FC } from "react"; export interface CircularTimeProgressProps { time: number; diff --git a/vinvoor/src/components/BrowserView.tsx b/vinvoor/src/components/BrowserView.tsx index bf97481..a77e53f 100644 --- a/vinvoor/src/components/BrowserView.tsx +++ b/vinvoor/src/components/BrowserView.tsx @@ -1,5 +1,6 @@ +import type { FC } from "react"; import { useMediaQuery, useTheme } from "@mui/material"; -import { FC, useEffect } from "react"; +import { useEffect } from "react"; interface BrowserViewProps { onMobileView?: () => void; @@ -16,11 +17,13 @@ export const BrowserView: FC = ({ const isMobileView = useMediaQuery(theme.breakpoints.down("md")); useEffect(() => { - if (isMobileView) onMobileView?.(); + if (isMobileView) + onMobileView?.(); else onBrowserView?.(); }, [isMobileView]); - if (isMobileView) return null; + if (isMobileView) + return null; return isMobileView ? null : children; }; diff --git a/vinvoor/src/components/DarkModeToggle.tsx b/vinvoor/src/components/DarkModeToggle.tsx index 65858dc..13b9c30 100644 --- a/vinvoor/src/components/DarkModeToggle.tsx +++ b/vinvoor/src/components/DarkModeToggle.tsx @@ -3,7 +3,7 @@ import { IconButton, Tooltip } from "@mui/material"; import { useContext } from "react"; import { ThemeContext } from "../providers/ThemeProvider"; -export const DarkModeToggle = () => { +export function DarkModeToggle() { const { themeMode, setTheme } = useContext(ThemeContext); const handleThemeChange = () => @@ -19,4 +19,4 @@ export const DarkModeToggle = () => { ); -}; +} diff --git a/vinvoor/src/components/LoadingSkeleton.tsx b/vinvoor/src/components/LoadingSkeleton.tsx index ff2a7fa..c1bdcae 100644 --- a/vinvoor/src/components/LoadingSkeleton.tsx +++ b/vinvoor/src/components/LoadingSkeleton.tsx @@ -1,6 +1,7 @@ -import { Skeleton, SkeletonProps } from "@mui/material"; -import { UseQueryResult } from "@tanstack/react-query"; -import { FC, ReactNode } from "react"; +import type { SkeletonProps } from "@mui/material"; +import type { UseQueryResult } from "@tanstack/react-query"; +import type { FC, ReactNode } from "react"; +import { Skeleton } from "@mui/material"; import { isResponseNot200Error } from "../util/fetch"; interface LoadingSkeletonProps extends SkeletonProps { @@ -14,11 +15,12 @@ export const LoadingSkeleton: FC = ({ ...props }) => { const isError = queries.some(query => query.isError); - if (isError) + if (isError) { throw ( - queries.find(query => isResponseNot200Error(query.error))?.error ?? - new Error("Error fetching data, unable to reach the server") + queries.find(query => isResponseNot200Error(query.error))?.error + ?? new Error("Error fetching data, unable to reach the server") ); + } const isLoading = queries.some(query => query.isLoading); diff --git a/vinvoor/src/components/ProtectedRoute.tsx b/vinvoor/src/components/ProtectedRoute.tsx index 5400de4..8783e63 100644 --- a/vinvoor/src/components/ProtectedRoute.tsx +++ b/vinvoor/src/components/ProtectedRoute.tsx @@ -1,4 +1,4 @@ -import { FC, ReactNode } from "react"; +import type { FC, ReactNode } from "react"; import { Navigate } from "react-router-dom"; import { useUser } from "../hooks/useUser"; @@ -9,7 +9,8 @@ interface ProtectedRouteProps { export const ProtectedRoute: FC = ({ children }) => { const { data: user } = useUser(); - if (!user?.admin) return ; + if (!user?.admin) + return ; return children; }; diff --git a/vinvoor/src/components/TypographyG.tsx b/vinvoor/src/components/TypographyG.tsx index 8d166f7..e223b57 100644 --- a/vinvoor/src/components/TypographyG.tsx +++ b/vinvoor/src/components/TypographyG.tsx @@ -1,6 +1,7 @@ -import { Typography, TypographyProps } from "@mui/material"; -import { FC } from "react"; +import type { TypographyProps } from "@mui/material"; +import type { FC } from "react"; +import { Typography } from "@mui/material"; -export const TypographyG: FC = props => { +export const TypographyG: FC = (props) => { return ; }; diff --git a/vinvoor/src/components/UnstyledLink.tsx b/vinvoor/src/components/UnstyledLink.tsx index 7891e70..a42a3f1 100644 --- a/vinvoor/src/components/UnstyledLink.tsx +++ b/vinvoor/src/components/UnstyledLink.tsx @@ -1,7 +1,8 @@ -import { FC } from "react"; -import { Link, LinkProps } from "react-router-dom"; +import type { FC } from "react"; +import type { LinkProps } from "react-router-dom"; +import { Link } from "react-router-dom"; -export const UnstyledLink: FC = props => { +export const UnstyledLink: FC = (props) => { return ( ); diff --git a/vinvoor/src/errors/ErrorPage.tsx b/vinvoor/src/errors/ErrorPage.tsx index 29fd06d..2c5c941 100644 --- a/vinvoor/src/errors/ErrorPage.tsx +++ b/vinvoor/src/errors/ErrorPage.tsx @@ -2,20 +2,23 @@ import { Box, Typography } from "@mui/material"; import { isRouteErrorResponse, useRouteError } from "react-router-dom"; import CursesSob from "/cursed_sob.png"; -const get_error = (error: unknown) => { +function get_error(error: unknown) { if (isRouteErrorResponse(error)) { return `${error.status} ${error.statusText}`; - } else if (error instanceof Error) { + } + else if (error instanceof Error) { return error.message; - } else if (typeof error === "string") { + } + else if (typeof error === "string") { return error; - } else { + } + else { console.error(error); return "Unknown error"; } -}; +} -export const ErrorPage = () => { +export function ErrorPage() { const error = useRouteError(); return ( @@ -42,4 +45,4 @@ export const ErrorPage = () => { ); -}; +} diff --git a/vinvoor/src/footer/Footer.tsx b/vinvoor/src/footer/Footer.tsx index 3336c34..17090ab 100644 --- a/vinvoor/src/footer/Footer.tsx +++ b/vinvoor/src/footer/Footer.tsx @@ -3,7 +3,7 @@ import { TypographyG } from "../components/TypographyG"; import { useVersion } from "../hooks/useVersion"; import ZeusIcon from "/zeus.svg"; -export const Footer = () => { +export function Footer() { const { data: version } = useVersion(); return ( @@ -14,7 +14,10 @@ export const Footer = () => { alignItems: "center", }} > - v {version?.version ?? ""} + + v + {version?.version ?? ""} + { ); -}; +} diff --git a/vinvoor/src/hooks/admin/useAdminDays.ts b/vinvoor/src/hooks/admin/useAdminDays.ts index 2f0afa7..931df69 100644 --- a/vinvoor/src/hooks/admin/useAdminDays.ts +++ b/vinvoor/src/hooks/admin/useAdminDays.ts @@ -1,27 +1,31 @@ +import type { Dayjs } from "dayjs"; +import type { Day, DayJSON } from "../../types/days"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { convertDayJSON, Day, DayJSON } from "../../types/days"; +import { convertDayJSON } from "../../types/days"; import { deleteAPI, getApi, postApi } from "../../util/fetch"; -import { Dayjs } from "dayjs"; const ENDPOINT = "admin/days"; -export const useAdminDays = () => - useQuery({ +export function useAdminDays() { + return useQuery({ queryKey: ["adminDays"], - queryFn: () => getApi(ENDPOINT, convertDayJSON), + queryFn: async () => getApi(ENDPOINT, convertDayJSON), retry: 1, }); +} -export const useAdminAddDay = () => - useMutation({ - mutationFn: (args: { startDate: Dayjs; endDate: Dayjs }) => +export function useAdminAddDay() { + return useMutation({ + mutationFn: async (args: { startDate: Dayjs; endDate: Dayjs }) => postApi(ENDPOINT, { start_date: args.startDate.format("YYYY-MM-DD"), end_date: args.endDate.format("YYYY-MM-DD"), }), }); +} -export const useAdminDeleteDay = () => - useMutation({ - mutationFn: (id: number) => deleteAPI(`${ENDPOINT}/${id}`), +export function useAdminDeleteDay() { + return useMutation({ + mutationFn: async (id: number) => deleteAPI(`${ENDPOINT}/${id}`), }); +} diff --git a/vinvoor/src/hooks/admin/useAdminSeason.ts b/vinvoor/src/hooks/admin/useAdminSeason.ts index bc6fbab..0231aed 100644 --- a/vinvoor/src/hooks/admin/useAdminSeason.ts +++ b/vinvoor/src/hooks/admin/useAdminSeason.ts @@ -1,29 +1,32 @@ +import type { Dayjs } from "dayjs"; +import type { Season, SeasonJSON } from "../../types/seasons"; import { useMutation, useQuery } from "@tanstack/react-query"; +import { convertSeasonJSON } from "../../types/seasons"; import { deleteAPI, getApi, postApi } from "../../util/fetch"; -import { Dayjs } from "dayjs"; -import { convertSeasonJSON, Season, SeasonJSON } from "../../types/seasons"; const ENDPOINT = "admin/seasons"; -export const useAdminSeasons = () => { +export function useAdminSeasons() { return useQuery({ queryKey: ["adminSeasons"], - queryFn: () => getApi(ENDPOINT, convertSeasonJSON), + queryFn: async () => getApi(ENDPOINT, convertSeasonJSON), retry: 1, }); -}; +} -export const useAdminAddSeason = () => - useMutation({ - mutationFn: (args: { name: string; startDate: Dayjs; endDate: Dayjs }) => +export function useAdminAddSeason() { + return useMutation({ + mutationFn: async (args: { name: string; startDate: Dayjs; endDate: Dayjs }) => postApi(ENDPOINT, { name: args.name, start: args.startDate.format("YYYY-MM-DD"), end: args.endDate.format("YYYY-MM-DD"), }), }); +} -export const useAdminDeleteSeason = () => - useMutation({ - mutationFn: (id: number) => deleteAPI(`${ENDPOINT}/${id}`), +export function useAdminDeleteSeason() { + return useMutation({ + mutationFn: async (id: number) => deleteAPI(`${ENDPOINT}/${id}`), }); +} diff --git a/vinvoor/src/hooks/useCard.ts b/vinvoor/src/hooks/useCard.ts index d9e0574..a098f2a 100644 --- a/vinvoor/src/hooks/useCard.ts +++ b/vinvoor/src/hooks/useCard.ts @@ -1,21 +1,23 @@ +import type { Card, CardJSON } from "../types/cards"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { Card, CardJSON, convertCardJSON } from "../types/cards"; +import { convertCardJSON } from "../types/cards"; import { getApi, patchApi } from "../util/fetch"; const ENDPOINT = "cards"; -export const useCards = () => - useQuery({ +export function useCards() { + return useQuery({ queryKey: ["cards"], - queryFn: () => getApi(ENDPOINT, convertCardJSON), + queryFn: async () => getApi(ENDPOINT, convertCardJSON), retry: 1, }); +} -export const usePatchCards = () => { +export function usePatchCards() { return useMutation({ - mutationFn: (args: { id: number; newName: string }) => + mutationFn: async (args: { id: number; newName: string }) => patchApi(`${ENDPOINT}/${args.id}`, { name: args.newName, }), }); -}; +} diff --git a/vinvoor/src/hooks/useLeaderboard.ts b/vinvoor/src/hooks/useLeaderboard.ts index 5301b0f..a1cb9f8 100644 --- a/vinvoor/src/hooks/useLeaderboard.ts +++ b/vinvoor/src/hooks/useLeaderboard.ts @@ -1,20 +1,23 @@ +import type { + LeaderboardItem, + LeaderboardItemJSON, +} from "../types/leaderboard"; import { useQuery } from "@tanstack/react-query"; -import { getApi } from "../util/fetch"; import { convertLeaderboardItemJSON, - LeaderboardItem, - LeaderboardItemJSON, } from "../types/leaderboard"; +import { getApi } from "../util/fetch"; const ENDPOINT = "leaderboard"; -export const useLeaderboardItems = () => - useQuery({ +export function useLeaderboardItems() { + return useQuery({ queryKey: ["leaderboard"], - queryFn: () => + queryFn: async () => getApi( ENDPOINT, convertLeaderboardItemJSON, ), retry: 1, }); +} diff --git a/vinvoor/src/hooks/useScan.ts b/vinvoor/src/hooks/useScan.ts index 24ca8b7..4f18f55 100644 --- a/vinvoor/src/hooks/useScan.ts +++ b/vinvoor/src/hooks/useScan.ts @@ -1,12 +1,14 @@ +import type { Scan, ScanJSON } from "../types/scans"; import { useQuery } from "@tanstack/react-query"; +import { convertScanJSON } from "../types/scans"; import { getApi } from "../util/fetch"; -import { convertScanJSON, Scan, ScanJSON } from "../types/scans"; const ENDPOINT = "scans"; -export const useScans = () => - useQuery({ +export function useScans() { + return useQuery({ queryKey: ["scans"], - queryFn: () => getApi(ENDPOINT, convertScanJSON), + queryFn: async () => getApi(ENDPOINT, convertScanJSON), retry: 1, }); +} diff --git a/vinvoor/src/hooks/useSeasons.ts b/vinvoor/src/hooks/useSeasons.ts index f5221a7..e573f1b 100644 --- a/vinvoor/src/hooks/useSeasons.ts +++ b/vinvoor/src/hooks/useSeasons.ts @@ -1,19 +1,21 @@ -import { MutateOptions, useQuery } from "@tanstack/react-query"; +import type { MutateOptions } from "@tanstack/react-query"; +import type { Season, SeasonJSON } from "../types/seasons"; +import { useQuery } from "@tanstack/react-query"; +import { convertSeasonJSON } from "../types/seasons"; import { getApi } from "../util/fetch"; -import { convertSeasonJSON, Season, SeasonJSON } from "../types/seasons"; import { usePatchSettings } from "./useSettings"; const ENDPOINT = "seasons"; -export const useSeasons = () => { +export function useSeasons() { return useQuery({ queryKey: ["seasons"], - queryFn: () => getApi(ENDPOINT, convertSeasonJSON), + queryFn: async () => getApi(ENDPOINT, convertSeasonJSON), retry: 1, }); -}; +} -export const useSetSeason = () => { +export function useSetSeason() { const { mutate, ...other } = usePatchSettings(); const setSeason = ( @@ -27,4 +29,4 @@ export const useSetSeason = () => { ) => mutate({ season: id }, options); return { setSeason, ...other }; -}; +} diff --git a/vinvoor/src/hooks/useSettings.ts b/vinvoor/src/hooks/useSettings.ts index 3432b31..0346cef 100644 --- a/vinvoor/src/hooks/useSettings.ts +++ b/vinvoor/src/hooks/useSettings.ts @@ -1,25 +1,27 @@ +import type { Settings, SettingsJSON } from "../types/settings"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { converSettingsJSON } from "../types/settings"; import { getApi, patchApi } from "../util/fetch"; -import { converSettingsJSON, Settings, SettingsJSON } from "../types/settings"; const ENDPOINT = "settings"; -export const useSettings = () => - useQuery({ +export function useSettings() { + return useQuery({ queryKey: ["settings"], - queryFn: () => getApi(ENDPOINT, converSettingsJSON), + queryFn: async () => getApi(ENDPOINT, converSettingsJSON), retry: 1, }); +} -export const usePatchSettings = () => { +export function usePatchSettings() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (args: Record) => + mutationFn: async (args: Record) => patchApi(ENDPOINT, args), - onSuccess: () => + onSuccess: async () => queryClient.invalidateQueries({ predicate: query => query.queryKey[0] !== "settings", }), }); -}; +} diff --git a/vinvoor/src/hooks/useUser.ts b/vinvoor/src/hooks/useUser.ts index db25b63..52ffaab 100644 --- a/vinvoor/src/hooks/useUser.ts +++ b/vinvoor/src/hooks/useUser.ts @@ -1,18 +1,20 @@ +import type { User, UserJSON } from "../types/user"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { convertUserJSON } from "../types/user"; import { getApi, isResponseNot200Error, postApi } from "../util/fetch"; -import { convertUserJSON, User, UserJSON } from "../types/user"; const ENDPOINT = "user"; -export const useUser = () => - useQuery({ +export function useUser() { + return useQuery({ queryKey: ["user"], queryFn: async () => { let user = {} as User; try { user = await getApi(ENDPOINT, convertUserJSON); - } catch (error) { + } + catch (error) { if (!isResponseNot200Error(error)) throw new Error("Failed to fetch user"); } @@ -21,15 +23,16 @@ export const useUser = () => }, retry: 1, }); +} -export const useLogout = () => { +export function useLogout() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: () => postApi("logout"), - onSuccess: () => + mutationFn: async () => postApi("logout"), + onSuccess: async () => queryClient.invalidateQueries({ queryKey: ["user"], }), }); -}; +} diff --git a/vinvoor/src/hooks/useVersion.ts b/vinvoor/src/hooks/useVersion.ts index b1087ec..8deec45 100644 --- a/vinvoor/src/hooks/useVersion.ts +++ b/vinvoor/src/hooks/useVersion.ts @@ -1,12 +1,14 @@ +import type { Version, VersionJSON } from "../types/version"; import { useQuery } from "@tanstack/react-query"; -import { convertVersionJSON, Version, VersionJSON } from "../types/version"; +import { convertVersionJSON } from "../types/version"; import { getApi } from "../util/fetch"; const ENDPOINT = "version"; -export const useVersion = () => - useQuery({ +export function useVersion() { + return useQuery({ queryKey: ["version"], - queryFn: () => getApi(ENDPOINT, convertVersionJSON), + queryFn: async () => getApi(ENDPOINT, convertVersionJSON), retry: 1, }); +} diff --git a/vinvoor/src/leaderboard/Leaderboard.tsx b/vinvoor/src/leaderboard/Leaderboard.tsx index e69385d..3f083ac 100644 --- a/vinvoor/src/leaderboard/Leaderboard.tsx +++ b/vinvoor/src/leaderboard/Leaderboard.tsx @@ -1,10 +1,10 @@ import { Divider, Paper, Table, TableContainer } from "@mui/material"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; +import { useLeaderboardItems } from "../hooks/useLeaderboard"; import { LeaderboardTableBody } from "./LeaderboardTableBody"; import { LeaderboardTableToolbar } from "./LeaderboardTableToolbar"; -import { useLeaderboardItems } from "../hooks/useLeaderboard"; -export const Leaderboard = () => { +export function Leaderboard() { const leaderboardQuery = useLeaderboardItems(); return ( @@ -20,4 +20,4 @@ export const Leaderboard = () => { ); -}; +} diff --git a/vinvoor/src/leaderboard/LeaderboardTableBody.tsx b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx index 2057208..eaaa2e7 100644 --- a/vinvoor/src/leaderboard/LeaderboardTableBody.tsx +++ b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx @@ -1,3 +1,6 @@ +import type { Theme } from "@mui/material/styles"; +import type { TableHeadCell } from "../types/general"; +import type { LeaderboardItem } from "../types/leaderboard"; import { Icon, TableBody, @@ -5,11 +8,10 @@ import { TableRow, Typography, } from "@mui/material"; -import { alpha, Theme, useTheme } from "@mui/material/styles"; +import { alpha, useTheme } from "@mui/material/styles"; import { useLeaderboardItems } from "../hooks/useLeaderboard"; import { useUser } from "../hooks/useUser"; -import { TableHeadCell } from "../types/general"; -import { leaderboardHeadCells, LeaderboardItem } from "../types/leaderboard"; +import { leaderboardHeadCells } from "../types/leaderboard"; import FirstPlaceIcon from "/first_place.svg"; import SecondPlaceIcon from "/second_place.svg"; import ThirdPlaceIcon from "/third_place.svg"; @@ -26,21 +28,23 @@ const leaderboardText = [ { fontSize: "18px", fontWeight: "bold" }, ]; -const getLeaderboardColor = (index: number, theme: Theme) => - leaderboardColors[index] +function getLeaderboardColor(index: number, theme: Theme) { + return leaderboardColors[index] ? { backgroundColor: leaderboardColors[index](theme) } : {}; +} const getLeaderboardText = (index: number) => leaderboardText[index] || {}; -const getPositionChange = (positionChange: number) => { +function getPositionChange(positionChange: number) { let color = "text.primary"; let prefix = ""; if (positionChange > 0) { color = "success.light"; prefix = "+"; - } else if (positionChange < 0) { + } + else if (positionChange < 0) { color = "error.light"; } @@ -50,9 +54,9 @@ const getPositionChange = (positionChange: number) => { {positionChange !== 0 && positionChange} ); -}; +} -const getPosition = (position: number) => { +function getPosition(position: number) { switch (position) { case 1: return ( @@ -75,12 +79,9 @@ const getPosition = (position: number) => { default: return {position}; } -}; +} -const getCell = ( - row: LeaderboardItem, - headCell: TableHeadCell, -) => { +function getCell(row: LeaderboardItem, headCell: TableHeadCell) { switch (headCell.id) { case "positionChange": return getPositionChange(row[headCell.id]); @@ -97,15 +98,16 @@ const getCell = ( ); } -}; +} -export const LeaderboardTableBody = () => { +export function LeaderboardTableBody() { const { data: rows } = useLeaderboardItems(); - if (!rows) return null; // Can never happen - const theme = useTheme(); const { data: user } = useUser(); + if (!rows) + return null; // Can never happen + return ( {rows.map((row, index) => { @@ -141,4 +143,4 @@ export const LeaderboardTableBody = () => { })} ); -}; +} diff --git a/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx b/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx index 2987440..5db2e9b 100644 --- a/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx +++ b/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx @@ -3,7 +3,7 @@ import { Button, Toolbar, Typography } from "@mui/material"; import { HashLink } from "react-router-hash-link"; import { useUser } from "../hooks/useUser"; -export const LeaderboardTableToolbar = () => { +export function LeaderboardTableToolbar() { const { data: user } = useUser(); return ( @@ -19,4 +19,4 @@ export const LeaderboardTableToolbar = () => { ); -}; +} diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index ba5b187..359b98d 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -1,27 +1,27 @@ -import "@fontsource/roboto/300.css"; -import "@fontsource/roboto/400.css"; -import "@fontsource/roboto/500.css"; -import "@fontsource/roboto/700.css"; import { CssBaseline } from "@mui/material"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ConfirmProvider } from "material-ui-confirm"; import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import "react-tooltip/dist/react-tooltip.css"; import { App } from "./App.tsx"; +import { Login } from "./auth/Login.tsx"; +import { Logout } from "./auth/Logout.tsx"; import { Cards } from "./cards/Cards.tsx"; import { ProtectedRoute } from "./components/ProtectedRoute.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; import { Leaderboard } from "./leaderboard/Leaderboard.tsx"; +import { NavBar } from "./navbar/NavBar.tsx"; import { CustomSnackbarProvider } from "./providers/CustomSnackbarProvider.tsx"; import { ThemeProvider } from "./providers/ThemeProvider.tsx"; import { Scans } from "./scans/Scans.tsx"; import { Admin } from "./settings/admin/Admin.tsx"; import { SettingsOverview } from "./settings/SettingsOverview.tsx"; -import { Login } from "./auth/Login.tsx"; -import { Logout } from "./auth/Logout.tsx"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { NavBar } from "./navbar/NavBar.tsx"; +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; +import "react-tooltip/dist/react-tooltip.css"; const queryClient = new QueryClient(); diff --git a/vinvoor/src/navbar/NavBar.tsx b/vinvoor/src/navbar/NavBar.tsx index 176382f..7d8ca7a 100644 --- a/vinvoor/src/navbar/NavBar.tsx +++ b/vinvoor/src/navbar/NavBar.tsx @@ -30,7 +30,7 @@ const userMenuPages: PageIcon[] = [ { page: "Settings", icon: }, ]; -export const NavBar = () => { +export function NavBar() { const { data: user } = useUser(); const [selectedPage, setSelectedPage] = useState(""); const showSeasons = useMediaQuery("(min-width:400px)"); @@ -110,4 +110,4 @@ export const NavBar = () => { ); -}; +} diff --git a/vinvoor/src/navbar/NavBarLogo.tsx b/vinvoor/src/navbar/NavBarLogo.tsx index 7b953fe..c59aed2 100644 --- a/vinvoor/src/navbar/NavBarLogo.tsx +++ b/vinvoor/src/navbar/NavBarLogo.tsx @@ -1,6 +1,8 @@ -import { Box, Button, SxProps, Theme, Typography } from "@mui/material"; +import type { SxProps, Theme } from "@mui/material"; +import type { FC } from "react"; +import { Box, Button, Typography } from "@mui/material"; import { HexagonSlice6 } from "mdi-material-ui"; -import { FC, useContext } from "react"; +import { useContext } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; import { ThemeContext } from "../providers/ThemeProvider"; @@ -23,18 +25,21 @@ export const NavBarLogo: FC = ({ }) => { const { setTheme } = useContext(ThemeContext); const handleClick = () => { - if (handleSelectedPage) handleSelectedPage("home"); + if (handleSelectedPage) + handleSelectedPage("home"); if (pressedAmount < CLICK_AMOUNT) { - if (pressedAmount === 0) startTimePress = Date.now(); + if (pressedAmount === 0) + startTimePress = Date.now(); pressedAmount++; if ( - pressedAmount === CLICK_AMOUNT && - Date.now() - startTimePress <= CLICK_TIME_MS - ) + pressedAmount === CLICK_AMOUNT + && Date.now() - startTimePress <= CLICK_TIME_MS + ) { setTheme("kak"); + } } }; diff --git a/vinvoor/src/navbar/NavBarPages.tsx b/vinvoor/src/navbar/NavBarPages.tsx index 1d626c1..fb4c660 100644 --- a/vinvoor/src/navbar/NavBarPages.tsx +++ b/vinvoor/src/navbar/NavBarPages.tsx @@ -1,7 +1,8 @@ -import { Box, Button, SxProps, Theme, Typography } from "@mui/material"; -import { FC } from "react"; +import type { SxProps, Theme } from "@mui/material"; +import type { FC } from "react"; +import type { PageIcon } from "./NavBar"; +import { Box, Button, Typography } from "@mui/material"; import { UnstyledLink } from "../components/UnstyledLink"; -import { PageIcon } from "./NavBar"; interface NavBarPagesProps { pageIcons: readonly PageIcon[]; diff --git a/vinvoor/src/navbar/NavBarSandwich.tsx b/vinvoor/src/navbar/NavBarSandwich.tsx index 2c6b18f..ba9571c 100644 --- a/vinvoor/src/navbar/NavBarSandwich.tsx +++ b/vinvoor/src/navbar/NavBarSandwich.tsx @@ -1,16 +1,19 @@ +import type { + SxProps, + Theme, +} from "@mui/material"; +import type { FC, MouseEvent } from "react"; +import type { PageIcon } from "./NavBar"; import MenuIcon from "@mui/icons-material/Menu"; import { Box, IconButton, Menu, MenuItem, - SxProps, - Theme, Typography, } from "@mui/material"; -import { FC, MouseEvent, useState } from "react"; +import { useState } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; -import { PageIcon } from "./NavBar"; interface NavBarSandwichProps { pageIcons: readonly PageIcon[]; diff --git a/vinvoor/src/navbar/NavBarSeasons.tsx b/vinvoor/src/navbar/NavBarSeasons.tsx index 85c776b..bb096ed 100644 --- a/vinvoor/src/navbar/NavBarSeasons.tsx +++ b/vinvoor/src/navbar/NavBarSeasons.tsx @@ -1,10 +1,11 @@ -import { useState, MouseEvent } from "react"; +import type { MouseEvent } from "react"; +import { ArrowDropDown, Refresh } from "@mui/icons-material"; +import { Button, IconButton, Menu, MenuItem, Typography } from "@mui/material"; +import { useState } from "react"; import { useSeasons, useSetSeason } from "../hooks/useSeasons"; import { useSettings } from "../hooks/useSettings"; -import { Button, IconButton, Menu, MenuItem, Typography } from "@mui/material"; -import { ArrowDropDown, Refresh } from "@mui/icons-material"; -export const NavBarSeasons = () => { +export function NavBarSeasons() { const { data: seasons } = useSeasons(); const { data: settings, refetch } = useSettings(); const { setSeason } = useSetSeason(); @@ -89,4 +90,4 @@ export const NavBarSeasons = () => { )} ); -}; +} diff --git a/vinvoor/src/navbar/NavBarUserMenu.tsx b/vinvoor/src/navbar/NavBarUserMenu.tsx index 78c493c..6ffc464 100644 --- a/vinvoor/src/navbar/NavBarUserMenu.tsx +++ b/vinvoor/src/navbar/NavBarUserMenu.tsx @@ -1,3 +1,5 @@ +import type { FC, MouseEvent } from "react"; +import type { PageIcon } from "./NavBar"; import { Button, Divider, @@ -8,13 +10,12 @@ import { } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { Cow, ExitRun, ShieldAccountOutline } from "mdi-material-ui"; -import { FC, MouseEvent, useState } from "react"; +import { useState } from "react"; import { Login } from "../auth/Login"; import { Logout } from "../auth/Logout"; import { BrowserView } from "../components/BrowserView"; import { UnstyledLink } from "../components/UnstyledLink"; import { useUser } from "../hooks/useUser"; -import { PageIcon } from "./NavBar"; interface NavBarUserMenuProps { pageIcons: readonly PageIcon[]; @@ -44,105 +45,109 @@ export const NavBarUserMenu: FC = ({ return ( <> - {user ? ( - <> - - - {pageIcons.map(({ page, icon }) => ( - - { - handleCloseUserMenu(); - if (isBrowserView) handleSelectedPage("user"); - }} - > - {icon} - {page} - - - ))} - - {user.admin && ( - + {user + ? ( + <> + + + {pageIcons.map(({ page, icon }) => ( + + { + handleCloseUserMenu(); + if (isBrowserView) + handleSelectedPage("user"); + }} + > + {icon} + {page} + + + ))} + + {user.admin && ( + + { + handleCloseUserMenu(); + if (isBrowserView) + handleSelectedPage("user"); + }} + sx={{ + "paddingX": "0", + "justifyContent": "center", + "backgroundColor": "error.dark", + "&:hover": { + backgroundColor: "error.light", + }, + }} + > + + Admin + + + )} { - handleCloseUserMenu(); - if (isBrowserView) handleSelectedPage("user"); - }} + onClick={handleCloseUserMenu} sx={{ paddingX: "0", justifyContent: "center", - backgroundColor: "error.dark", - "&:hover": { - backgroundColor: "error.light", - }, }} > - - Admin + + + {/* Hacky way to center it with the other icons */} + Logout + - - )} - - - - {/* Hacky way to center it with the other icons */} - Logout - - - - - ) : ( - - Login - - )} + + + ) + : ( + + Login + + )} ); }; diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx index ce60813..c3b0e5f 100644 --- a/vinvoor/src/overview/Overview.tsx +++ b/vinvoor/src/overview/Overview.tsx @@ -4,16 +4,16 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { Tooltip } from "react-tooltip"; import { BrowserView } from "../components/BrowserView"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; +import { useScans } from "../hooks/useScan"; +import { useSeasons } from "../hooks/useSeasons"; +import { useSettings } from "../hooks/useSettings"; import { CheckIn } from "./checkin/CheckIn"; import { Days } from "./days/Days"; import { Heatmap } from "./heatmap/Heatmap"; import { HeatmapVariant } from "./heatmap/types"; import { Streak } from "./streak/Streak"; -import { useScans } from "../hooks/useScan"; -import { useSeasons } from "../hooks/useSeasons"; -import { useSettings } from "../hooks/useSettings"; -export const Overview = () => { +export function Overview() { const scansQuery = useScans(); const seasonsQuery = useSeasons(); const settingsQuery = useSettings(); @@ -47,7 +47,8 @@ export const Overview = () => { seasons.length > 1 ? seasons[1].start : seasons[0].start, new Date(), ]); - } else { + } + else { setHeatmapDates([currentSeason.start, currentSeason.end]); } } @@ -55,71 +56,73 @@ export const Overview = () => { return ( - {scansQuery.data?.length ? ( - - - - - - - - - - - setChecked(false)}> - - Months - - Days + {scansQuery.data?.length + ? ( + + + + + + + + + + + setChecked(false)}> + + Months + + Days + + + + - - - - - - - - - + + + + + + + + + ) + : ( + + + + You don't have any scans. + + + Start scanning to see some data! + + - - - ) : ( - - - - You don't have any scans. - - - Start scanning to see some data! - - - - )} + )} ); -}; +} diff --git a/vinvoor/src/overview/checkin/CheckIn.tsx b/vinvoor/src/overview/checkin/CheckIn.tsx index 83bc60b..bf3578d 100644 --- a/vinvoor/src/overview/checkin/CheckIn.tsx +++ b/vinvoor/src/overview/checkin/CheckIn.tsx @@ -1,31 +1,34 @@ import { Alert, AlertTitle } from "@mui/material"; import { EmoticonExcitedOutline, EmoticonFrownOutline } from "mdi-material-ui"; -import { isTheSameDay } from "../../util/util"; import { useScans } from "../../hooks/useScan"; +import { isTheSameDay } from "../../util/util"; -export const CheckIn = () => { +export function CheckIn() { const { data: scans } = useScans(); - if (!scans) return null; // Can never happen + if (!scans) + return null; // Can never happen const checkedIn = isTheSameDay(scans[scans.length - 1].scanTime, new Date()); - return checkedIn ? ( - } - > - Checked in - Nice of you to stop by ! - - ) : ( - } - > - Not checked in - We miss you ! - - ); -}; + return checkedIn + ? ( + } + > + Checked in + Nice of you to stop by ! + + ) + : ( + } + > + Not checked in + We miss you ! + + ); +} diff --git a/vinvoor/src/overview/days/Days.tsx b/vinvoor/src/overview/days/Days.tsx index 4167946..43bb6a1 100644 --- a/vinvoor/src/overview/days/Days.tsx +++ b/vinvoor/src/overview/days/Days.tsx @@ -1,21 +1,22 @@ +import type { ApexOptions } from "apexcharts"; +import type { Scan } from "../../types/scans"; import { useTheme } from "@mui/material"; -import { ApexOptions } from "apexcharts"; import Chart from "react-apexcharts"; -import { Scan } from "../../types/scans"; import { useScans } from "../../hooks/useScan"; -const getDayCount = (scans: readonly Scan[]) => { +function getDayCount(scans: readonly Scan[]) { const days = [0, 0, 0, 0, 0, 0, 0]; - scans.forEach(scan => { + scans.forEach((scan) => { days[scan.scanTime.getDay() - 1]++; }); return days.slice(0, -2) as ApexNonAxisChartSeries; -}; +} -export const Days = () => { +export function Days() { const theme = useTheme(); const { data: scans } = useScans(); - if (!scans) return null; // Can never happen + if (!scans) + return null; // Can never happen const state = { options: { @@ -65,4 +66,4 @@ export const Days = () => { return ( ); -}; +} diff --git a/vinvoor/src/overview/heatmap/Day.tsx b/vinvoor/src/overview/heatmap/Day.tsx index 4732712..df596e6 100644 --- a/vinvoor/src/overview/heatmap/Day.tsx +++ b/vinvoor/src/overview/heatmap/Day.tsx @@ -1,8 +1,9 @@ +import type { FC } from "react"; +import type { DayData, HeatmapVariant } from "./types"; import { useTheme } from "@mui/material"; -import { FC, useMemo } from "react"; -import "./heatmap.css"; +import { useMemo } from "react"; +import { useScans } from "../../hooks/useScan"; import { Rect } from "./Rect"; -import { DayData, HeatmapVariant } from "./types"; import { DATE_FORMATTER, DAYS_IN_WEEK, @@ -14,7 +15,7 @@ import { styleMonth, WEEKS_IN_MONTH, } from "./utils"; -import { useScans } from "../../hooks/useScan"; +import "./heatmap.css"; interface DayProps { startDate: Date; @@ -35,17 +36,16 @@ export const Day: FC = ({ }) => { const theme = useTheme(); const { data: scans } = useScans(); - if (!scans) return null; // Can never happen const data = useMemo(() => { - const normalizedScans = [...scans]; + const normalizedScans = [...scans ?? []]; // normalizedScans.forEach(scan => scan.scanTime.setHours(0, 0, 0, 0)); const formattedScans = formatData(normalizedScans); const start = new Date( - startDate.getTime() - - startDate.getDay() * MILLISECONDS_IN_DAY + - MILLISECONDS_IN_DAY, + startDate.getTime() + - startDate.getDay() * MILLISECONDS_IN_DAY + + MILLISECONDS_IN_DAY, ); const startDates = Array.from( @@ -58,7 +58,8 @@ export const Day: FC = ({ while (newStartDate.getDay() !== 1) { newStartDate.setDate(newStartDate.getDate() - 1); } - } else { + } + else { newStartDate.setMonth(newStartDate.getMonth() + idx); newStartDate.setDate(1); while (newStartDate.getDay() !== 1) { @@ -71,9 +72,9 @@ export const Day: FC = ({ ); const endWeek = new Date( - endDate.getTime() + - MILLISECONDS_IN_DAY * - (DAYS_IN_WEEK - (getMondayIndexedDay(endDate) % DAYS_IN_WEEK)), + endDate.getTime() + + MILLISECONDS_IN_DAY + * (DAYS_IN_WEEK - (getMondayIndexedDay(endDate) % DAYS_IN_WEEK)), ); return { @@ -84,6 +85,9 @@ export const Day: FC = ({ }; }, [scans, startDate, endDate]); + if (!scans) + return null; // Can never happen + return ( {Array.from({ length: columnCount }, (_, idx) => { @@ -91,79 +95,82 @@ export const Day: FC = ({ {isDayVariant(variant) ? Array.from({ length: DAYS_IN_WEEK }, (_, cidx) => { - const currentDate = new Date( - data.start.getTime() + - MILLISECONDS_IN_DAY * (idx * DAYS_IN_WEEK + cidx), - ); - - if (currentDate.getTime() < startDate.getTime()) return null; - - if (currentDate.getTime() > endDate.getTime()) return null; - - let colors = theme.heatmap.colorInActive; - if (data.data[currentDate.getTime()]) - colors = theme.heatmap.colorActive; - - const dataTooltipContent = `${ - data.data[currentDate.getTime()] ? "Present" : "Absent" - } on ${DATE_FORMATTER.format(currentDate)}`; - - return ( - - ); - }) + const currentDate = new Date( + data.start.getTime() + + MILLISECONDS_IN_DAY * (idx * DAYS_IN_WEEK + cidx), + ); + + if (currentDate.getTime() < startDate.getTime()) + return null; + + if (currentDate.getTime() > endDate.getTime()) + return null; + + let colors = theme.heatmap.colorInActive; + if (data.data[currentDate.getTime()]) + colors = theme.heatmap.colorActive; + + const dataTooltipContent = `${ + data.data[currentDate.getTime()] ? "Present" : "Absent" + } on ${DATE_FORMATTER.format(currentDate)}`; + + return ( + + ); + }) : Array.from({ length: WEEKS_IN_MONTH }, (_, cidx) => { - const currentDate = new Date( - data.startDates[idx].getTime() + - MILLISECONDS_IN_DAY * cidx * DAYS_IN_WEEK, - ); - - // Week is no longer in the month - if ( - currentDate.getMonth() > startDate.getMonth() + idx && - getMondayIndexedDay(currentDate) <= - currentDate.getDate() - 1 - ) - return null; - - // Week is after end date - if (currentDate.getTime() >= data.endWeek.getTime()) - return null; - - const count = Array.from( - { length: DAYS_IN_WEEK }, - (_, i) => - new Date(currentDate.getTime() + i * MILLISECONDS_IN_DAY), - ).filter( - date => - date.getTime() <= endDate.getTime() && - data.data[date.getTime()], - ).length; - - const colors = styleMonth[Math.min(count, 5)](theme); // Can be higher than 5 if multiple scans in a day or scanned during the weekend - - const dataTooltipContent = `${count} scan${ - count !== 1 ? "s" : "" - } in the week of ${DATE_FORMATTER.format(currentDate)}`; - - return ( - - ); - })} + const currentDate = new Date( + data.startDates[idx].getTime() + + MILLISECONDS_IN_DAY * cidx * DAYS_IN_WEEK, + ); + + // Week is no longer in the month + if ( + currentDate.getMonth() > startDate.getMonth() + idx + && getMondayIndexedDay(currentDate) + <= currentDate.getDate() - 1 + ) { + return null; + } + + // Week is after end date + if (currentDate.getTime() >= data.endWeek.getTime()) + return null; + + const count = Array.from( + { length: DAYS_IN_WEEK }, + (_, i) => + new Date(currentDate.getTime() + i * MILLISECONDS_IN_DAY), + ).filter( + date => + date.getTime() <= endDate.getTime() + && data.data[date.getTime()], + ).length; + + const colors = styleMonth[Math.min(count, 5)](theme); // Can be higher than 5 if multiple scans in a day or scanned during the weekend + + const dataTooltipContent = `${count} scan${ + count !== 1 ? "s" : "" + } in the week of ${DATE_FORMATTER.format(currentDate)}`; + + return ( + + ); + })} ); })} diff --git a/vinvoor/src/overview/heatmap/Heatmap.tsx b/vinvoor/src/overview/heatmap/Heatmap.tsx index fe1ae27..351805d 100644 --- a/vinvoor/src/overview/heatmap/Heatmap.tsx +++ b/vinvoor/src/overview/heatmap/Heatmap.tsx @@ -1,8 +1,8 @@ +import type { FC } from "react"; +import type { HeatmapVariant } from "./types"; import { useMediaQuery, useTheme } from "@mui/material"; -import { FC } from "react"; import { Day } from "./Day"; import { LabelsMonth } from "./LabelsMonth"; -import { HeatmapVariant } from "./types"; import { DAYS_IN_WEEK, getColumnCountDays, @@ -35,12 +35,12 @@ export const Heatmap: FC = ({ startDate, endDate, variant }) => { return ( = ({ {(isDayVariant(variant) ? data.day : data.month).map((item, idx) => { return ( { +// Consts + +export const DAYS_IN_WEEK = 7; +export const WEEKS_IN_MONTH = 5; +export const MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24; + +export function getColumnCountDays(startDate: Date, endDate: Date) { const startOfWeek = new Date(startDate); startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay()); @@ -12,43 +19,44 @@ export const getColumnCountDays = (startDate: Date, endDate: Date) => { else endOfWeek.setDate(endOfWeek.getDate() - endOfWeek.getDay() + 6); return Math.ceil( - (endOfWeek.getTime() - startOfWeek.getTime()) / - (DAYS_IN_WEEK * MILLISECONDS_IN_DAY), + (endOfWeek.getTime() - startOfWeek.getTime()) + / (DAYS_IN_WEEK * MILLISECONDS_IN_DAY), ); -}; +} -export const getColumnCountMonths = (startDate: Date, endDate: Date) => { +export function getColumnCountMonths(startDate: Date, endDate: Date) { return ( - (endDate.getFullYear() - startDate.getFullYear()) * 12 + - endDate.getMonth() - - startDate.getMonth() + - 1 + (endDate.getFullYear() - startDate.getFullYear()) * 12 + + endDate.getMonth() + - startDate.getMonth() + + 1 ); -}; +} export const getMondayIndexedDay = (date: Date) => (date.getDay() + 6) % 7; -const getNormalizedTime = (date: Date) => { +function getNormalizedTime(date: Date) { const result = new Date(date); result.setHours(0, 0, 0, 0); return result; -}; +} -export const formatData = (scans: Scan[]) => { +export function formatData(scans: Scan[]) { const result: Record = {}; - scans.forEach(scan => { + scans.forEach((scan) => { const date = getNormalizedTime(scan.scanTime); result[date.getTime()] = { - date: date, + date, count: 1, }; }); return result; -}; +} -export const isDayVariant = (variant: HeatmapVariant) => - variant === HeatmapVariant.DAYS; +export function isDayVariant(variant: HeatmapVariant) { + return variant === HeatmapVariant.DAYS; +} export const styleMonth = [ (theme: Theme) => theme.heatmap.color0, @@ -96,9 +104,3 @@ export const DATE_FORMATTER = new Intl.DateTimeFormat("en-GB", { month: "short", day: "numeric", }); - -// Consts - -export const DAYS_IN_WEEK = 7; -export const WEEKS_IN_MONTH = 5; -export const MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24; diff --git a/vinvoor/src/overview/streak/Streak.tsx b/vinvoor/src/overview/streak/Streak.tsx index c92f36c..6e3fd31 100644 --- a/vinvoor/src/overview/streak/Streak.tsx +++ b/vinvoor/src/overview/streak/Streak.tsx @@ -1,34 +1,36 @@ +import type { Scan } from "../../types/scans"; import { Box, Typography } from "@mui/material"; -import { Scan } from "../../types/scans"; +import { useScans } from "../../hooks/useScan"; import { isTheSameDay, MILLISECONDS_IN_ONE_DAY, shiftDate, } from "../../util/util"; -import { useScans } from "../../hooks/useScan"; -const isWeekendBetween = (date1: Date, date2: Date) => { +function isWeekendBetween(date1: Date, date2: Date) { const diffDays = Math.floor( (date2.getTime() - date1.getTime()) / MILLISECONDS_IN_ONE_DAY, ); - if (diffDays > 2) return false; + if (diffDays > 2) + return false; return date1.getDay() === 5 && [1, 6, 7].includes(date2.getDay()); -}; +} -const isStreakDay = (date1: Date, date2: Date) => { - if (isTheSameDay(date1, shiftDate(date2, 1))) return true; +function isStreakDay(date1: Date, date2: Date) { + if (isTheSameDay(date1, shiftDate(date2, 1))) + return true; if (date1.getDay() === 5 && [1, 6, 7].includes(date2.getDay())) return isWeekendBetween(date1, date2); return false; -}; +} -const getStreak = (scans: readonly Scan[]): [boolean, number] => { +function getStreak(scans: readonly Scan[]): [boolean, number] { const dates = scans - .map(scan => { + .map((scan) => { const date = new Date(scan.scanTime); date.setHours(0, 0, 0, 0); return date; @@ -42,60 +44,67 @@ const getStreak = (scans: readonly Scan[]): [boolean, number] => { let streak = 0; - const isOnStreak = - isTheSameDay(dates[dates.length - 1], new Date()) || - isWeekendBetween(dates[dates.length - 1], new Date()); + const isOnStreak + = isTheSameDay(dates[dates.length - 1], new Date()) + || isWeekendBetween(dates[dates.length - 1], new Date()); if (isOnStreak) { let i = dates.length; streak++; while (i-- > 1 && isStreakDay(dates[i], dates[i - 1])) streak++; - } else { - streak = - dates.length > 0 + } + else { + streak + = dates.length > 0 ? Math.floor( - (new Date().getTime() - dates[dates.length - 1].getTime()) / - MILLISECONDS_IN_ONE_DAY - - 1, - ) + (new Date().getTime() - dates[dates.length - 1].getTime()) + / MILLISECONDS_IN_ONE_DAY + - 1, + ) : 0; } return [isOnStreak, streak]; -}; +} -export const Streak = () => { +export function Streak() { const { data: scans } = useScans(); - if (!scans) return null; // Can never happen + if (!scans) + return null; // Can never happen const [isOnStreak, streak] = getStreak(scans); const color = isOnStreak ? "primary" : "error"; const textEnd = isOnStreak ? "streak" : "absent"; - return streak === 0 ? ( - `1px solid ${theme.palette.primary.main}`} - borderBottom={theme => `1px solid ${theme.palette.primary.main}`} - > - - Scan to retain streak - - - ) : ( - - - {streak} - - - day{streak > 1 ? "s" : ""} {textEnd} - - - ); -}; + return streak === 0 + ? ( + `1px solid ${theme.palette.primary.main}`} + borderBottom={theme => `1px solid ${theme.palette.primary.main}`} + > + + Scan to retain streak + + + ) + : ( + + + {streak} + + + day + {streak > 1 ? "s" : ""} + {" "} + {textEnd} + + + ); +} diff --git a/vinvoor/src/providers/CustomSnackbarProvider.tsx b/vinvoor/src/providers/CustomSnackbarProvider.tsx index caf051d..58f37ba 100644 --- a/vinvoor/src/providers/CustomSnackbarProvider.tsx +++ b/vinvoor/src/providers/CustomSnackbarProvider.tsx @@ -1,10 +1,12 @@ +import type { + SnackbarProviderProps, +} from "notistack"; +import type { FC } from "react"; import { styled, useTheme } from "@mui/material/styles"; import { MaterialDesignContent, SnackbarProvider, - SnackbarProviderProps, } from "notistack"; -import { FC } from "react"; export const CustomSnackbarProvider: FC = ({ children, diff --git a/vinvoor/src/providers/ThemeProvider.tsx b/vinvoor/src/providers/ThemeProvider.tsx index 6608456..b253bfa 100644 --- a/vinvoor/src/providers/ThemeProvider.tsx +++ b/vinvoor/src/providers/ThemeProvider.tsx @@ -1,7 +1,12 @@ +import type { FC, ReactNode } from "react"; +import type { ThemeMode } from "../themes/theme"; import { ThemeProvider as MUIThemeProvider } from "@mui/material"; import Cookies from "js-cookie"; -import { createContext, FC, ReactNode, useEffect, useState } from "react"; -import { ThemeMode, themeModes } from "../themes/theme"; +import { createContext, useEffect, useState } from "react"; +import { themeModes } from "../themes/theme"; + +/* eslint-disable react-refresh/only-export-components, react/no-unstable-context-value */ +// TODO interface ThemeProviderProps { children: ReactNode; diff --git a/vinvoor/src/scans/Scans.tsx b/vinvoor/src/scans/Scans.tsx index 6d5812d..691d886 100644 --- a/vinvoor/src/scans/Scans.tsx +++ b/vinvoor/src/scans/Scans.tsx @@ -1,11 +1,11 @@ import { Paper, Table, TableContainer } from "@mui/material"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; +import { useCards } from "../hooks/useCard"; +import { useScans } from "../hooks/useScan"; import { ScansTableBody } from "./ScansTableBody"; import { ScansTableHead } from "./ScansTableHead"; -import { useScans } from "../hooks/useScan"; -import { useCards } from "../hooks/useCard"; -export const Scans = () => { +export function Scans() { const scansQuery = useScans(); const cardsQuery = useCards(); @@ -21,4 +21,4 @@ export const Scans = () => { ); -}; +} diff --git a/vinvoor/src/scans/ScansTableBody.tsx b/vinvoor/src/scans/ScansTableBody.tsx index 03598b3..6e3e5ff 100644 --- a/vinvoor/src/scans/ScansTableBody.tsx +++ b/vinvoor/src/scans/ScansTableBody.tsx @@ -1,28 +1,31 @@ +import type { ScanCard } from "../types/scans"; import { TableBody, TableCell, TableRow, Typography } from "@mui/material"; import { useEffect, useState } from "react"; -import { mergeScansCards, ScanCard, scanCardHeadCells } from "../types/scans"; import { useCards } from "../hooks/useCard"; import { useScans } from "../hooks/useScan"; +import { mergeScansCards, scanCardHeadCells } from "../types/scans"; -export const ScansTableBody = () => { +export function ScansTableBody() { const { data: scans } = useScans(); const { data: cards } = useCards(); - if (!scans || !cards) return null; // Can never happen const [scanCards, setScanCards] = useState([]); useEffect(() => { - const mergedScansCards = mergeScansCards(scans, cards); + const mergedScansCards = mergeScansCards(scans ?? [], cards ?? []); mergedScansCards.sort( (a, b) => b.scanTime.getTime() - a.scanTime.getTime(), ); setScanCards(mergedScansCards); }, [scans, cards]); + if (!scans || !cards) + return null; // Can never happen + return ( - {scanCards.map((scanCard, index) => ( - + {scanCards.map(scanCard => ( + {scanCardHeadCells.map(headCell => ( { ))} ); -}; +} diff --git a/vinvoor/src/scans/ScansTableHead.tsx b/vinvoor/src/scans/ScansTableHead.tsx index 380ad01..227da60 100644 --- a/vinvoor/src/scans/ScansTableHead.tsx +++ b/vinvoor/src/scans/ScansTableHead.tsx @@ -1,7 +1,7 @@ import { TableCell, TableHead, TableRow, Typography } from "@mui/material"; import { scanCardHeadCells } from "../types/scans"; -export const ScansTableHead = () => { +export function ScansTableHead() { return ( @@ -13,4 +13,4 @@ export const ScansTableHead = () => { ); -}; +} diff --git a/vinvoor/src/settings/Settings.tsx b/vinvoor/src/settings/Settings.tsx index e43bd38..730ed22 100644 --- a/vinvoor/src/settings/Settings.tsx +++ b/vinvoor/src/settings/Settings.tsx @@ -5,7 +5,6 @@ import { MenuItem, Paper, Select, - SelectChangeEvent, Stack, Tooltip, Typography, @@ -30,20 +29,26 @@ const handleDeleteContent = ( ); -export const Settings = () => { +export function Settings() { const { data: settingsTruth, refetch } = useSettings(); const { data: seasons } = useSeasons(); - if (!settingsTruth || !seasons) return null; // Can never happen const patchSettings = usePatchSettings(); const [settings, setSettings] = useState({ ...settingsTruth }); const { enqueueSnackbar } = useSnackbar(); const confirm = useConfirm(); + useEffect(() => { + setSettings({ ...settingsTruth, season: settingsTruth?.season }); + }, [settingsTruth?.season]); + + if (!settingsTruth || !seasons) + return null; // Can never happen + const handleSeasonChange = (event: SelectChangeEvent) => setSettings({ ...settings, - season: parseInt(event.target.value), + season: Number.parseInt(event.target.value), }); const handleSubmit = () => { @@ -74,10 +79,6 @@ export const Settings = () => { }); }; - useEffect(() => { - setSettings({ ...settingsTruth, season: settingsTruth.season }); - }, [settingsTruth.season]); - return ( { value={ seasons .find(season => season.id === settings.season) - ?.id.toString() ?? seasons[0].id.toString() + ?.id + .toString() ?? seasons[0].id.toString() } onChange={handleSeasonChange} sx={{ ml: "20px" }} @@ -150,4 +152,4 @@ export const Settings = () => { ); -}; +} diff --git a/vinvoor/src/settings/SettingsOverview.tsx b/vinvoor/src/settings/SettingsOverview.tsx index dff72f3..1ea66ad 100644 --- a/vinvoor/src/settings/SettingsOverview.tsx +++ b/vinvoor/src/settings/SettingsOverview.tsx @@ -2,7 +2,7 @@ import { LoadingSkeleton } from "../components/LoadingSkeleton"; import { useSettings } from "../hooks/useSettings"; import { Settings } from "./Settings"; -export const SettingsOverview = () => { +export function SettingsOverview() { const settingsQuery = useSettings(); return ( @@ -10,4 +10,4 @@ export const SettingsOverview = () => { ); -}; +} diff --git a/vinvoor/src/settings/admin/Admin.tsx b/vinvoor/src/settings/admin/Admin.tsx index 246665b..1f944a8 100644 --- a/vinvoor/src/settings/admin/Admin.tsx +++ b/vinvoor/src/settings/admin/Admin.tsx @@ -1,5 +1,5 @@ +import type { FC } from "react"; import { Alert, Grid, Typography } from "@mui/material"; -import { FC } from "react"; import { Days } from "./days/Days"; import { Seasons } from "./seasons/Seasons"; diff --git a/vinvoor/src/settings/admin/days/Days.tsx b/vinvoor/src/settings/admin/days/Days.tsx index b495276..d757cf4 100644 --- a/vinvoor/src/settings/admin/days/Days.tsx +++ b/vinvoor/src/settings/admin/days/Days.tsx @@ -1,10 +1,10 @@ import { Grid } from "@mui/material"; import { LoadingSkeleton } from "../../../components/LoadingSkeleton"; +import { useAdminDays } from "../../../hooks/admin/useAdminDays"; import { DaysAdd } from "./DaysAdd"; import { DaysTable } from "./DaysTable"; -import { useAdminDays } from "../../../hooks/admin/useAdminDays"; -export const Days = () => { +export function Days() { const daysQuery = useAdminDays(); return ( @@ -24,4 +24,4 @@ export const Days = () => { ); -}; +} diff --git a/vinvoor/src/settings/admin/days/DaysAdd.tsx b/vinvoor/src/settings/admin/days/DaysAdd.tsx index 26000e2..9fef7ea 100644 --- a/vinvoor/src/settings/admin/days/DaysAdd.tsx +++ b/vinvoor/src/settings/admin/days/DaysAdd.tsx @@ -1,16 +1,18 @@ +import type { Dayjs } from "dayjs"; +import type { Dispatch, SetStateAction } from "react"; import { Box, Button, Paper, Stack } from "@mui/material"; import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; -import dayjs, { Dayjs } from "dayjs"; +import dayjs from "dayjs"; import { useSnackbar } from "notistack"; -import { Dispatch, SetStateAction, useState } from "react"; +import { useState } from "react"; import { TypographyG } from "../../../components/TypographyG"; import { useAdminAddDay, useAdminDays, } from "../../../hooks/admin/useAdminDays"; -export const DaysAdd = () => { +export function DaysAdd() { const { refetch } = useAdminDays(); const addDay = useAdminAddDay(); const [startDate, setStartDate] = useState(dayjs()); @@ -85,4 +87,4 @@ export const DaysAdd = () => { ); -}; +} diff --git a/vinvoor/src/settings/admin/days/DaysTable.tsx b/vinvoor/src/settings/admin/days/DaysTable.tsx index 42e3667..8a3da5d 100644 --- a/vinvoor/src/settings/admin/days/DaysTable.tsx +++ b/vinvoor/src/settings/admin/days/DaysTable.tsx @@ -1,46 +1,46 @@ +import type { ChangeEvent } from "react"; +import type { Day } from "../../../types/days"; +import type { Optional } from "../../../types/general"; import { Paper, Stack, Table, TableContainer } from "@mui/material"; import { useSnackbar } from "notistack"; -import { ChangeEvent, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { TypographyG } from "../../../components/TypographyG"; -import { Day } from "../../../types/days"; -import { Optional } from "../../../types/general"; -import { randomInt } from "../../../util/util"; -import { DaysTableBody } from "./DaysTableBody"; -import { DaysTableHead } from "./DaysTableHead"; -import { DaysTableToolbar } from "./DaysTableToolbar"; import { useAdminDays, useAdminDeleteDay, } from "../../../hooks/admin/useAdminDays"; import { useAdminSeasons } from "../../../hooks/admin/useAdminSeason"; +import { randomInt } from "../../../util/util"; +import { DaysTableBody } from "./DaysTableBody"; +import { DaysTableHead } from "./DaysTableHead"; +import { DaysTableToolbar } from "./DaysTableToolbar"; -export const DaysTable = () => { +export function DaysTable() { const { data: days, refetch } = useAdminDays(); const { data: seasons } = useAdminSeasons(); - if (!days) return null; // Can never happen const deleteDay = useAdminDeleteDay(); - const [rows, setRows] = useState(days); + const [rows, setRows] = useState(days ?? []); const [selected, setSelected] = useState([]); const [deleting, setDeleting] = useState(false); const [dateFilter, setDateFilter] = useState< [Optional, Optional] >([undefined, undefined]); - const [seasonsFilter, setSeasonsFilter] = - useState>(undefined); + const [seasonsFilter, setSeasonsFilter] + = useState>(undefined); const [weekdaysFilter, setWeekdaysFilter] = useState(false); const [weekendsFilter, setWeekendsFilter] = useState(false); const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const filterDays = (): readonly Day[] => { - let filteredDays = [...days]; + let filteredDays = [...days ?? []]; if (dateFilter[0] !== undefined && dateFilter[1] !== undefined) { filteredDays = filteredDays.filter( day => - day.date.getTime() >= dateFilter[0]!.getTime() && - day.date.getTime() <= dateFilter[1]!.getTime(), + day.date.getTime() >= dateFilter[0]!.getTime() + && day.date.getTime() <= dateFilter[1]!.getTime(), ); } if (seasonsFilter) { @@ -65,12 +65,20 @@ export const DaysTable = () => { return filteredDays; }; + useEffect( + () => setRows(filterDays()), + [days, dateFilter, seasonsFilter, weekdaysFilter, weekendsFilter], + ); + + if (!days) + return null; // Can never happen + const handleDelete = () => { setDeleting(true); const key = randomInt(); enqueueSnackbar("Deleting...", { variant: "info", - key: key, + key, persist: true, }); @@ -127,17 +135,13 @@ export const DaysTable = () => { }; const handleSelectAll = (event: ChangeEvent) => { - if (event.target.checked) setSelected(rows.map(day => day.id)); + if (event.target.checked) + setSelected(rows.map(day => day.id)); else setSelected([]); }; const isSelected = (id: number) => selected.includes(id); - useEffect( - () => setRows(filterDays()), - [days, dateFilter, seasonsFilter, weekdaysFilter, weekendsFilter], - ); - return ( { ); -}; +} diff --git a/vinvoor/src/settings/admin/days/DaysTableBody.tsx b/vinvoor/src/settings/admin/days/DaysTableBody.tsx index b83de5a..af5c6c8 100644 --- a/vinvoor/src/settings/admin/days/DaysTableBody.tsx +++ b/vinvoor/src/settings/admin/days/DaysTableBody.tsx @@ -1,3 +1,5 @@ +import type { FC, ReactNode } from "react"; +import type { Day } from "../../../types/days"; import DeleteIcon from "@mui/icons-material/Delete"; import { Checkbox, @@ -8,12 +10,11 @@ import { Typography, } from "@mui/material"; import { useSnackbar } from "notistack"; -import { FC, ReactNode } from "react"; -import { Day, daysHeadCells } from "../../../types/days"; import { useAdminDays, useAdminDeleteDay, } from "../../../hooks/admin/useAdminDays"; +import { daysHeadCells } from "../../../types/days"; interface DaysTableBodyProps { rows: readonly Day[]; @@ -34,7 +35,8 @@ export const DaysTableBody: FC = ({ const { enqueueSnackbar } = useSnackbar(); const handleClick = (id: number) => { - if (isSelected(id)) handleSelect(id); // This will remove it from the selected list + if (isSelected(id)) + handleSelect(id); // This will remove it from the selected list deleteDay.mutate(id, { onSuccess: () => { diff --git a/vinvoor/src/settings/admin/days/DaysTableHead.tsx b/vinvoor/src/settings/admin/days/DaysTableHead.tsx index 14f7412..d169d7a 100644 --- a/vinvoor/src/settings/admin/days/DaysTableHead.tsx +++ b/vinvoor/src/settings/admin/days/DaysTableHead.tsx @@ -1,3 +1,4 @@ +import type { ChangeEvent, FC } from "react"; import { Box, Button, @@ -7,7 +8,6 @@ import { TableRow, Typography, } from "@mui/material"; -import { ChangeEvent, FC } from "react"; import { daysHeadCells } from "../../../types/days"; interface DaysTableHeadProps { @@ -50,9 +50,11 @@ export const DaysTableHead: FC = ({ variant="outlined" disabled={numSelected === 0 || deleting} onClick={handleDelete} - >{`Delet${deleting ? "ing" : "e"} ${numSelected} ${ - deleting ? "..." : "" - }`} + > + {`Delet${deleting ? "ing" : "e"} ${numSelected} ${ + deleting ? "..." : "" + }`} + diff --git a/vinvoor/src/settings/admin/days/DaysTableToolbar.tsx b/vinvoor/src/settings/admin/days/DaysTableToolbar.tsx index fa0f17d..9411944 100644 --- a/vinvoor/src/settings/admin/days/DaysTableToolbar.tsx +++ b/vinvoor/src/settings/admin/days/DaysTableToolbar.tsx @@ -1,16 +1,20 @@ +import type { + SelectChangeEvent, +} from "@mui/material"; +import type { Dayjs } from "dayjs"; +import type { ChangeEvent, Dispatch, FC, SetStateAction } from "react"; +import type { Optional } from "../../../types/general"; import { Checkbox, MenuItem, Select, - SelectChangeEvent, Stack, Typography, } from "@mui/material"; import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; -import dayjs, { Dayjs } from "dayjs"; -import { ChangeEvent, Dispatch, FC, SetStateAction, useState } from "react"; -import { Optional } from "../../../types/general"; +import dayjs from "dayjs"; +import { useState } from "react"; import { useAdminDays } from "../../../hooks/admin/useAdminDays"; import { useAdminSeasons } from "../../../hooks/admin/useAdminSeason"; @@ -37,17 +41,19 @@ export const DaysTableToolbar: FC = ({ }) => { const { data: days } = useAdminDays(); const { data: seasons } = useAdminSeasons(); - if (!days || !seasons) return null; // Can never happen const [startDate, setStartDate] = useState( - days.length ? dayjs(days[0].date) : dayjs(), + days?.length ? dayjs(days[0].date) : dayjs(), ); const [endDate, setEndDate] = useState( - days.length ? dayjs(days[days.length - 1].date) : dayjs(), + days?.length ? dayjs(days[days.length - 1].date) : dayjs(), ); - const [selectedSeason, setSelectedSeason] = - useState>(seasonFilter); + const [selectedSeason, setSelectedSeason] + = useState>(seasonFilter); + + if (!days || !seasons) + return null; // Can never happen const handleDateChange = ( date: Dayjs | null, @@ -70,10 +76,11 @@ export const DaysTableToolbar: FC = ({ }; const handleSeasonChange = (event: SelectChangeEvent) => - setSelectedSeason(parseInt(event.target.value)); + setSelectedSeason(Number.parseInt(event.target.value)); const handleClickSeason = (event: ChangeEvent) => { - if (event.target.checked) setSeasonFilter(selectedSeason); + if (event.target.checked) + setSeasonFilter(selectedSeason); else setSeasonFilter(undefined); }; @@ -119,7 +126,8 @@ export const DaysTableToolbar: FC = ({ value={ seasons .find(season => season.id === selectedSeason) - ?.id.toString() ?? seasons[0].id.toString() + ?.id + .toString() ?? seasons[0].id.toString() } onChange={handleSeasonChange} > diff --git a/vinvoor/src/settings/admin/seasons/Seasons.tsx b/vinvoor/src/settings/admin/seasons/Seasons.tsx index e02dd61..bf729c6 100644 --- a/vinvoor/src/settings/admin/seasons/Seasons.tsx +++ b/vinvoor/src/settings/admin/seasons/Seasons.tsx @@ -1,10 +1,10 @@ import { Grid } from "@mui/material"; import { LoadingSkeleton } from "../../../components/LoadingSkeleton"; -import { SeasonsTable } from "./SeasonsTable"; -import { SeasonsAdd } from "./SeasonsAdd"; import { useAdminSeasons } from "../../../hooks/admin/useAdminSeason"; +import { SeasonsAdd } from "./SeasonsAdd"; +import { SeasonsTable } from "./SeasonsTable"; -export const Seasons = () => { +export function Seasons() { const seasonsQuery = useAdminSeasons(); return ( @@ -24,4 +24,4 @@ export const Seasons = () => { ); -}; +} diff --git a/vinvoor/src/settings/admin/seasons/SeasonsAdd.tsx b/vinvoor/src/settings/admin/seasons/SeasonsAdd.tsx index f308d4a..6168982 100644 --- a/vinvoor/src/settings/admin/seasons/SeasonsAdd.tsx +++ b/vinvoor/src/settings/admin/seasons/SeasonsAdd.tsx @@ -1,16 +1,18 @@ -import dayjs, { Dayjs } from "dayjs"; -import { - useAdminAddSeason, - useAdminSeasons, -} from "../../../hooks/admin/useAdminSeason"; -import { Dispatch, SetStateAction, useState } from "react"; -import { useSnackbar } from "notistack"; +import type { Dayjs } from "dayjs"; +import type { Dispatch, SetStateAction } from "react"; import { Box, Button, Paper, Stack, TextField } from "@mui/material"; import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import dayjs from "dayjs"; +import { useSnackbar } from "notistack"; +import { useState } from "react"; import { TypographyG } from "../../../components/TypographyG"; +import { + useAdminAddSeason, + useAdminSeasons, +} from "../../../hooks/admin/useAdminSeason"; -export const SeasonsAdd = () => { +export function SeasonsAdd() { const { refetch } = useAdminSeasons(); const addSeason = useAdminAddSeason(); const [startDate, setStartDate] = useState(dayjs()); @@ -95,4 +97,4 @@ export const SeasonsAdd = () => { ); -}; +} diff --git a/vinvoor/src/settings/admin/seasons/SeasonsTable.tsx b/vinvoor/src/settings/admin/seasons/SeasonsTable.tsx index 1963ad3..a3fb173 100644 --- a/vinvoor/src/settings/admin/seasons/SeasonsTable.tsx +++ b/vinvoor/src/settings/admin/seasons/SeasonsTable.tsx @@ -1,31 +1,32 @@ +import type { ChangeEvent } from "react"; import { Paper, Stack, Table, TableContainer } from "@mui/material"; -import { TypographyG } from "../../../components/TypographyG"; -import { ChangeEvent, useState } from "react"; -import { randomInt } from "../../../util/util"; import { useSnackbar } from "notistack"; +import { useState } from "react"; +import { TypographyG } from "../../../components/TypographyG"; import { useAdminDeleteSeason, useAdminSeasons, } from "../../../hooks/admin/useAdminSeason"; -import { SeasonsTableHead } from "./SeasonsTableHead"; +import { randomInt } from "../../../util/util"; import { SeasonsTableBody } from "./SeasonsTableBody"; +import { SeasonsTableHead } from "./SeasonsTableHead"; -export const SeasonsTable = () => { +export function SeasonsTable() { const { data: seasons, refetch } = useAdminSeasons(); - if (!seasons) return null; // Can never happen - const deleteSeason = useAdminDeleteSeason(); const [selected, setSelected] = useState([]); const [deleting, setDeleting] = useState(false); - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + if (!seasons) + return null; // Can never happen + const handleDelete = () => { setDeleting(true); const key = randomInt(); enqueueSnackbar("Deleting...", { variant: "info", - key: key, + key, persist: true, }); @@ -79,7 +80,8 @@ export const SeasonsTable = () => { }; const handleSelectAll = (event: ChangeEvent) => { - if (event.target.checked) setSelected(seasons.map(season => season.id)); + if (event.target.checked) + setSelected(seasons.map(season => season.id)); else setSelected([]); }; @@ -115,4 +117,4 @@ export const SeasonsTable = () => { ); -}; +} diff --git a/vinvoor/src/settings/admin/seasons/SeasonsTableBody.tsx b/vinvoor/src/settings/admin/seasons/SeasonsTableBody.tsx index 0409a96..0b31ec9 100644 --- a/vinvoor/src/settings/admin/seasons/SeasonsTableBody.tsx +++ b/vinvoor/src/settings/admin/seasons/SeasonsTableBody.tsx @@ -1,3 +1,5 @@ +import type { FC, ReactNode } from "react"; +import DeleteIcon from "@mui/icons-material/Delete"; import { Checkbox, IconButton, @@ -6,8 +8,6 @@ import { TableRow, Typography, } from "@mui/material"; -import DeleteIcon from "@mui/icons-material/Delete"; -import { FC, ReactNode } from "react"; import { useSnackbar } from "notistack"; import { useAdminDeleteSeason, @@ -27,14 +27,15 @@ export const SeasonsTableBody: FC = ({ deleting, }) => { const { data: seasons, refetch } = useAdminSeasons(); - if (!seasons) return null; // Can never happen - const deleteSeason = useAdminDeleteSeason(); const { enqueueSnackbar } = useSnackbar(); + if (!seasons) + return null; // Can never happen + const handleClick = (id: number) => { - console.log("Hi"); - if (isSelected(id)) handleSelect(id); // This will remove it from the selected list + if (isSelected(id)) + handleSelect(id); // This will remove it from the selected list deleteSeason.mutate(id, { onSuccess: () => { diff --git a/vinvoor/src/settings/admin/seasons/SeasonsTableHead.tsx b/vinvoor/src/settings/admin/seasons/SeasonsTableHead.tsx index 91c40b5..4e58828 100644 --- a/vinvoor/src/settings/admin/seasons/SeasonsTableHead.tsx +++ b/vinvoor/src/settings/admin/seasons/SeasonsTableHead.tsx @@ -1,3 +1,4 @@ +import type { ChangeEvent, FC } from "react"; import { Box, Button, @@ -7,7 +8,6 @@ import { TableRow, Typography, } from "@mui/material"; -import { ChangeEvent, FC } from "react"; import { seasonsHeadCells } from "../../../types/seasons"; interface SeasonsTableHeadProps { @@ -50,9 +50,11 @@ export const SeasonsTableHead: FC = ({ variant="outlined" disabled={numSelected === 0 || deleting} onClick={handleDelete} - >{`Delet${deleting ? "ing" : "e"} ${numSelected} ${ - deleting ? "..." : "" - }`} + > + {`Delet${deleting ? "ing" : "e"} ${numSelected} ${ + deleting ? "..." : "" + }`} + diff --git a/vinvoor/src/themes/theme.ts b/vinvoor/src/themes/theme.ts index 866fc4a..0034e20 100644 --- a/vinvoor/src/themes/theme.ts +++ b/vinvoor/src/themes/theme.ts @@ -1,4 +1,5 @@ -import { createTheme, ThemeOptions } from "@mui/material"; +import type { ThemeOptions } from "@mui/material"; +import { createTheme } from "@mui/material"; const baseTheme: ThemeOptions = { palette: { @@ -134,7 +135,7 @@ export const kakTheme = createTheme({ MuiButton: { styleOverrides: { outlined: { - borderWidth: "2px", + "borderWidth": "2px", "&:hover": { borderWidth: "2px", }, diff --git a/vinvoor/src/types/cards.ts b/vinvoor/src/types/cards.ts index a105562..a7afca2 100644 --- a/vinvoor/src/types/cards.ts +++ b/vinvoor/src/types/cards.ts @@ -1,4 +1,4 @@ -import { Base, BaseJSON, TableHeadCell } from "./general"; +import type { Base, BaseJSON, TableHeadCell } from "./general"; // External @@ -46,28 +46,29 @@ export interface CardGetRegisterResponse { // Converters -export const convertCardJSON = (cardsJSON: CardJSON[]): Card[] => - cardsJSON.map(cardJSON => ({ +export function convertCardJSON(cardsJSON: CardJSON[]): Card[] { + return cardsJSON.map(cardJSON => ({ ...cardJSON, lastUsed: new Date(cardJSON.last_used), amountUsed: cardJSON.amount_used, createdAt: new Date(cardJSON.created_at), })); +} -export const convertCardPostResponseJSON = ( - cardJSON: CardPostResponseJSON, -): CardPostResponse => ({ - isCurrentUser: cardJSON.is_current_user, -}); +export function convertCardPostResponseJSON(cardJSON: CardPostResponseJSON): CardPostResponse { + return { + isCurrentUser: cardJSON.is_current_user, + }; +} -export const convertCardGetRegisterResponseJSON = ( - cardJSON: CardGetRegisterResponseJSON, -): CardGetRegisterResponse => ({ - ...cardJSON, - isCurrentUser: cardJSON.is_current_user, - timeRemaining: cardJSON.time_remaining, - timePercentage: cardJSON.time_percentage, -}); +export function convertCardGetRegisterResponseJSON(cardJSON: CardGetRegisterResponseJSON): CardGetRegisterResponse { + return { + ...cardJSON, + isCurrentUser: cardJSON.is_current_user, + timeRemaining: cardJSON.time_remaining, + timePercentage: cardJSON.time_percentage, + }; +} // Table @@ -90,7 +91,8 @@ export const cardsHeadCells: readonly TableHeadCell[] = [ align: "right", padding: "normal", convert: (value: Date) => { - if (value.getFullYear() === 1) return "Not used"; + if (value.getFullYear() === 1) + return "Not used"; else return value.toDateString(); }, } as TableHeadCell, diff --git a/vinvoor/src/types/days.ts b/vinvoor/src/types/days.ts index 6824549..43d5653 100644 --- a/vinvoor/src/types/days.ts +++ b/vinvoor/src/types/days.ts @@ -1,4 +1,4 @@ -import { Base, BaseJSON, TableHeadCell } from "./general"; +import type { Base, BaseJSON, TableHeadCell } from "./general"; // External @@ -14,13 +14,14 @@ export interface Day extends Base { // Converters -export const convertDayJSON = (daysJSON: DayJSON[]): Day[] => - daysJSON +export function convertDayJSON(daysJSON: DayJSON[]): Day[] { + return daysJSON .map(dayJSON => ({ ...dayJSON, date: new Date(dayJSON.date), })) .sort((a, b) => a.date.getTime() - b.date.getTime()); +} // Table diff --git a/vinvoor/src/types/leaderboard.ts b/vinvoor/src/types/leaderboard.ts index 376916b..ffb6a06 100644 --- a/vinvoor/src/types/leaderboard.ts +++ b/vinvoor/src/types/leaderboard.ts @@ -1,4 +1,4 @@ -import { TableHeadCell } from "./general"; +import type { TableHeadCell } from "./general"; // External @@ -22,14 +22,13 @@ export interface LeaderboardItem { // Converters -export const convertLeaderboardItemJSON = ( - leaderboardItems: LeaderboardItemJSON[], -): LeaderboardItem[] => - leaderboardItems.map(leaderboardItem => ({ +export function convertLeaderboardItemJSON(leaderboardItems: LeaderboardItemJSON[]): LeaderboardItem[] { + return leaderboardItems.map(leaderboardItem => ({ ...leaderboardItem, totalDays: leaderboardItem.total_days, positionChange: leaderboardItem.position_change, })); +} // Table diff --git a/vinvoor/src/types/scans.ts b/vinvoor/src/types/scans.ts index 6e2fe7e..d6c67a2 100644 --- a/vinvoor/src/types/scans.ts +++ b/vinvoor/src/types/scans.ts @@ -1,6 +1,6 @@ +import type { Card } from "./cards"; +import type { Base, BaseJSON, TableHeadCell } from "./general"; import { dateTimeFormat } from "../util/util"; -import { Card } from "./cards"; -import { Base, BaseJSON, TableHeadCell } from "./general"; // External @@ -25,8 +25,8 @@ export interface ScanCard { // Converters -export const convertScanJSON = (scansJSON: ScanJSON[]): Scan[] => - scansJSON +export function convertScanJSON(scansJSON: ScanJSON[]): Scan[] { + return scansJSON .map(scanJSON => ({ ...scanJSON, scanTime: new Date(scanJSON.scan_time), @@ -34,6 +34,7 @@ export const convertScanJSON = (scansJSON: ScanJSON[]): Scan[] => createdAt: new Date(scanJSON.created_at), })) .sort((a, b) => a.scanTime.getTime() - b.scanTime.getTime()); +} // Table @@ -56,11 +57,9 @@ export const scanCardHeadCells: readonly TableHeadCell[] = [ // Other -export const mergeScansCards = ( - scans: readonly Scan[], - cards: readonly Card[], -): ScanCard[] => - scans.map(scan => ({ +export function mergeScansCards(scans: readonly Scan[], cards: readonly Card[]): ScanCard[] { + return scans.map(scan => ({ scanTime: scan.scanTime, card: cards.find(card => card.serial === scan.cardSerial), })); +} diff --git a/vinvoor/src/types/seasons.ts b/vinvoor/src/types/seasons.ts index a1baf91..529ada9 100644 --- a/vinvoor/src/types/seasons.ts +++ b/vinvoor/src/types/seasons.ts @@ -1,4 +1,4 @@ -import { Base, BaseJSON, TableHeadCell } from "./general"; +import type { Base, BaseJSON, TableHeadCell } from "./general"; // External @@ -20,13 +20,14 @@ export interface Season extends Base { // Converters -export const convertSeasonJSON = (seasonsJSON: SeasonJSON[]): Season[] => - seasonsJSON.map(seasonJSON => ({ +export function convertSeasonJSON(seasonsJSON: SeasonJSON[]): Season[] { + return seasonsJSON.map(seasonJSON => ({ ...seasonJSON, start: new Date(seasonJSON.start), end: new Date(seasonJSON.end), isCurrent: seasonJSON.is_current, })); +} // Table diff --git a/vinvoor/src/types/settings.ts b/vinvoor/src/types/settings.ts index c121da1..cf8555e 100644 --- a/vinvoor/src/types/settings.ts +++ b/vinvoor/src/types/settings.ts @@ -12,6 +12,8 @@ export interface Settings { // Converters -export const converSettingsJSON = (settingsJSON: SettingsJSON): Settings => ({ - ...settingsJSON, -}); +export function converSettingsJSON(settingsJSON: SettingsJSON): Settings { + return { + ...settingsJSON, + }; +} diff --git a/vinvoor/src/types/version.ts b/vinvoor/src/types/version.ts index 7cdc308..96cfd2b 100644 --- a/vinvoor/src/types/version.ts +++ b/vinvoor/src/types/version.ts @@ -12,6 +12,8 @@ export interface Version { // Converters -export const convertVersionJSON = (versionJSON: VersionJSON): Version => ({ - ...versionJSON, -}); +export function convertVersionJSON(versionJSON: VersionJSON): Version { + return { + ...versionJSON, + }; +} diff --git a/vinvoor/src/util/fetch.ts b/vinvoor/src/util/fetch.ts index f2e34ed..39dd217 100644 --- a/vinvoor/src/util/fetch.ts +++ b/vinvoor/src/util/fetch.ts @@ -2,17 +2,12 @@ const URLS: Record = { API: import.meta.env.VITE_BACKEND_URL as string, }; -export const getApi = ( - endpoint: string, - convertData?: (data: U) => T, -) => _fetch(`${URLS.API}/${endpoint}`, {}, convertData); +export async function getApi(endpoint: string, convertData?: (data: U) => T) { + return _fetch(`${URLS.API}/${endpoint}`, {}, convertData); +} -export const postApi = ( - endpoint: string, - body: Record = {}, - convertData?: (data: U) => T, -) => - _fetch( +export async function postApi(endpoint: string, body: Record = {}, convertData?: (data: U) => T) { + return _fetch( `${URLS.API}/${endpoint}`, { method: "POST", @@ -21,13 +16,10 @@ export const postApi = ( }, convertData, ); +} -export const patchApi = ( - endpoint: string, - body: Record = {}, - convertData?: (data: U) => T, -) => - _fetch( +export async function patchApi(endpoint: string, body: Record = {}, convertData?: (data: U) => T) { + return _fetch( `${URLS.API}/${endpoint}`, { method: "PATCH", @@ -36,36 +28,30 @@ export const patchApi = ( }, convertData, ); +} -export const deleteAPI = ( - endpoint: string, - body: Record = {}, -) => - _fetch(`${URLS.API}/${endpoint}`, { +export async function deleteAPI(endpoint: string, body: Record = {}) { + return _fetch(`${URLS.API}/${endpoint}`, { method: "DELETE", body: JSON.stringify(body), headers: new Headers({ "content-type": "application/json" }), }); +} interface ResponseNot200Error extends Error { response: Response; } -export const isResponseNot200Error = ( - error: unknown, -): error is ResponseNot200Error => - (error as ResponseNot200Error).response !== undefined; +export function isResponseNot200Error(error: unknown): error is ResponseNot200Error { + return (error as ResponseNot200Error).response !== undefined; +} -const _fetch = async ( - url: string, - options: RequestInit = {}, - convertData?: (data: U) => T, -): Promise => - fetch(url, { credentials: "include", ...options }) - .then(response => { +async function _fetch(url: string, options: RequestInit = {}, convertData?: (data: U) => T): Promise { + return fetch(url, { credentials: "include", ...options }) + .then(async (response) => { if (!response.ok) { const error = new Error( - "Fetch failed with status: " + response.status, + `Fetch failed with status: ${response.status}`, ) as ResponseNot200Error; error.response = response; throw error; @@ -73,8 +59,12 @@ const _fetch = async ( const contentType = response.headers.get("content-type"); - return contentType?.includes("application/json") - ? response.json() - : response.text(); + if (contentType?.includes("application/json")) + return response.json() as Promise; + else if (contentType?.includes("image/png")) + return response.blob(); + else + return response.text(); }) .then(data => (convertData ? convertData(data as U) : (data as T))); +} diff --git a/vinvoor/src/util/util.ts b/vinvoor/src/util/util.ts index 450bf78..fea8a86 100644 --- a/vinvoor/src/util/util.ts +++ b/vinvoor/src/util/util.ts @@ -1,21 +1,22 @@ -export const randomInt = (lower = 0, upper = 10000): number => { +export function randomInt(lower = 0, upper = 10000): number { return Math.floor(Math.random() * (upper - lower + 1) + lower); -}; +} // Date functions export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000; -export const isTheSameDay = (date1: Date, date2: Date) => - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate(); +export function isTheSameDay(date1: Date, date2: Date) { + return date1.getFullYear() === date2.getFullYear() + && date1.getMonth() === date2.getMonth() + && date1.getDate() === date2.getDate(); +} -export const shiftDate = (date: Date, numDays: number) => { +export function shiftDate(date: Date, numDays: number) { const newDate = new Date(date); newDate.setDate(newDate.getDate() + numDays); return newDate; -}; +} export const dateTimeFormat = new Intl.DateTimeFormat("en-GB", { year: "2-digit", @@ -27,42 +28,45 @@ export const dateTimeFormat = new Intl.DateTimeFormat("en-GB", { // Compare functions -export const equal = (left: unknown, right: unknown): boolean => { - if (typeof left !== typeof right) return false; +export function equal(left: unknown, right: unknown): boolean { + if (typeof left !== typeof right) + return false; if (Array.isArray(left) && Array.isArray(right)) return equalArray(left, right); - if (typeof left === "object" && left !== null && right !== null) + if (typeof left === "object" && left !== null && right !== null) { return equalObject( left as Record, right as Record, ); + } return left === right; -}; +} -const equalArray = (left: unknown[], right: unknown[]): boolean => { - if (left.length !== right.length) return false; +function equalArray(left: unknown[], right: unknown[]): boolean { + if (left.length !== right.length) + return false; for (let i = 0; i < left.length; i++) { - if (!equal(left[i], right[i])) return false; + if (!equal(left[i], right[i])) + return false; } return true; -}; +} -const equalObject = ( - left: Record, - right: Record, -): boolean => { +function equalObject(left: Record, right: Record): boolean { const leftKeys = Object.keys(left); const rightKeys = Object.keys(right); - if (leftKeys.length !== rightKeys.length) return false; + if (leftKeys.length !== rightKeys.length) + return false; for (const key of leftKeys) { - if (!equal(left[key], right[key])) return false; + if (!equal(left[key], right[key])) + return false; } return true; -}; +} diff --git a/vinvoor/tsconfig.json b/vinvoor/tsconfig.json index c5f9626..2ff90e9 100644 --- a/vinvoor/tsconfig.json +++ b/vinvoor/tsconfig.json @@ -1,25 +1,29 @@ { "compilerOptions": { "target": "ES2020", - "useDefineForClassFields": true, + "jsx": "react-jsx", "lib": ["ES2020", "DOM", "DOM.Iterable"], + "moduleDetection": "force", + "useDefineForClassFields": true, + "baseUrl": ".", "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, + "moduleResolution": "Bundler", + "paths": { + "@/*": ["./src/*"] + }, "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ + "allowImportingTsExtensions": true, + "allowJs": true, "strict": true, + "strictNullChecks": true, + "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noEmit": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "skipLibCheck": true }, - "include": ["src", "eslint.config.mjs"], - "references": [{ "path": "./tsconfig.node.json" }] + "exclude": ["dist", "node_modules", "cypress"] } diff --git a/vinvoor/tsconfig.node.json b/vinvoor/tsconfig.node.json index 97ede7e..38a9d93 100644 --- a/vinvoor/tsconfig.node.json +++ b/vinvoor/tsconfig.node.json @@ -1,11 +1,11 @@ { "compilerOptions": { "composite": true, - "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler", + "strict": true, "allowSyntheticDefaultImports": true, - "strict": true + "skipLibCheck": true }, "include": ["vite.config.ts"] }