diff --git a/eslint.config.js b/eslint.config.js index 4fa125d..5e6b472 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,28 +2,22 @@ import js from '@eslint/js' import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' import { defineConfig, globalIgnores } from 'eslint/config' export default defineConfig([ globalIgnores(['dist']), { - files: ['**/*.{js,jsx}'], + files: ['**/*.{ts,tsx}'], extends: [ js.configs.recommended, + tseslint.configs.recommended, reactHooks.configs.flat.recommended, reactRefresh.configs.vite, ], languageOptions: { ecmaVersion: 2020, globals: globals.browser, - parserOptions: { - ecmaVersion: 'latest', - ecmaFeatures: { jsx: true }, - sourceType: 'module', - }, - }, - rules: { - 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], }, }, ]) diff --git a/package-lock.json b/package-lock.json index c1d398f..b0399fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", @@ -24,6 +25,8 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", "vite": "^7.3.1" } }, @@ -768,15 +771,15 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -809,9 +812,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { @@ -822,7 +825,7 @@ "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -846,9 +849,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -1626,6 +1629,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1646,6 +1659,301 @@ "@types/react": "^19.2.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", @@ -1806,9 +2114,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001775", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", - "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", "dev": true, "funding": [ { @@ -1947,9 +2255,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "dev": true, "license": "ISC" }, @@ -2031,25 +2339,25 @@ } }, "node_modules/eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -2068,7 +2376,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2297,9 +2605,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true, "license": "ISC" }, @@ -2890,9 +3198,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -2998,9 +3306,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -3067,9 +3375,9 @@ } }, "node_modules/react-icons": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", - "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", "license": "MIT", "peerDependencies": { "react": "*" @@ -3292,6 +3600,19 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3305,6 +3626,51 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", + "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.57.0", + "@typescript-eslint/parser": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", diff --git a/package.json b/package.json index cba36c0..ab4fdc4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, @@ -19,6 +19,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", @@ -26,6 +27,8 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", "vite": "^7.3.1" } -} +} \ No newline at end of file diff --git a/src/App.jsx b/src/App.tsx similarity index 85% rename from src/App.jsx rename to src/App.tsx index 293a69e..ec50eb5 100644 --- a/src/App.jsx +++ b/src/App.tsx @@ -1,11 +1,12 @@ -// src/App.jsx +// src/App.tsx -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; +import Effects from './pages/Effects'; import Home from './pages/Home'; import Rules from './pages/Rules'; -import Effects from './pages/Effects'; import Scorepad from './pages/Scorepad'; + function App() { return ( diff --git a/src/components/EffectComponents.jsx b/src/components/EffectComponents.tsx similarity index 75% rename from src/components/EffectComponents.jsx rename to src/components/EffectComponents.tsx index 6103b0f..13d406f 100644 --- a/src/components/EffectComponents.jsx +++ b/src/components/EffectComponents.tsx @@ -1,6 +1,14 @@ -// src/components/EffectRow.jsx +// src/components/EffectComponents.tsx -export function SideEffect({ imageSrc, children, banding = true }) { +import React from 'react'; + +export interface SideEffectProps { + imageSrc: string; + children: React.ReactNode; + banding?: boolean; +} + +export function SideEffect({ imageSrc, children, banding = true }: SideEffectProps) { return (
@@ -13,17 +21,21 @@ export function SideEffect({ imageSrc, children, banding = true }) { ); } -export function TopEffect({ images, visuals, children, banding = true }) { +export interface TopEffectProps { + images?: string[]; + visuals?: React.ReactNode; + children: React.ReactNode; + banding?: boolean; +} + +export function TopEffect({ images, visuals, children, banding = true }: TopEffectProps) { return (
- {/* Renders custom complex grids if provided */} {visuals && (
{visuals}
)} - - {/* Renders the standard single row if a simple array is provided */} {images && images.length > 0 && (
{images.map((src, index) => ( @@ -31,7 +43,6 @@ export function TopEffect({ images, visuals, children, banding = true }) { ))}
)} -
{children}
@@ -39,7 +50,13 @@ export function TopEffect({ images, visuals, children, banding = true }) { ); } -export function InfoBox({ title, children, className = "self-center" }) { +export interface InfoBoxProps { + title?: string; + children: React.ReactNode; + className?: string; +} + +export function InfoBox({ title, children, className = "self-center" }: InfoBoxProps) { return (
{title &&

{title}

} @@ -48,7 +65,12 @@ export function InfoBox({ title, children, className = "self-center" }) { ); } -export function SpecialDescription({ title, children }) { +export interface SpecialDescriptionProps { + title?: string; + children: React.ReactNode; +} + +export function SpecialDescription({ title, children }: SpecialDescriptionProps) { return (
{title &&

{title}

} @@ -57,7 +79,12 @@ export function SpecialDescription({ title, children }) { ); } -export function SectionHeader({ logoSrc, title }) { +export interface SectionHeaderProps { + logoSrc: string; + title: string; +} + +export function SectionHeader({ logoSrc, title }: SectionHeaderProps) { return ( <>

@@ -79,7 +106,12 @@ export function SectionDivider() { ) } -export function SectionContainer({ id, children }) { +export interface SectionContainerProps { + id: string; + children: React.ReactNode; +} + +export function SectionContainer({ id, children }: SectionContainerProps) { return (
{children} @@ -87,7 +119,12 @@ export function SectionContainer({ id, children }) { ); } -export function Clarification({ type = "Clarification", children }) { +export interface AnnotationProps { + type?: string; + children: React.ReactNode; +} + +export function Clarification({ type = "Clarification", children }: AnnotationProps) { return (

{type}: {children} @@ -95,7 +132,7 @@ export function Clarification({ type = "Clarification", children }) { ); } -export function Note({ type = "Note", children }) { +export function Note({ type = "Note", children }: AnnotationProps) { return (

{type}: {children} diff --git a/src/components/InsetButton.jsx b/src/components/InsetButton.tsx similarity index 77% rename from src/components/InsetButton.jsx rename to src/components/InsetButton.tsx index e71d7c9..d575332 100644 --- a/src/components/InsetButton.jsx +++ b/src/components/InsetButton.tsx @@ -1,8 +1,13 @@ -// src/components/InsetButton.jsx +// src/components/InsetButton.tsx import { Link } from 'react-router-dom'; -export default function InsetButton({ to, title }) { +interface InsetButtonProps { + to: string; + title: string; +} + +export default function InsetButton({ to, title }: InsetButtonProps) { return ( (false); return (

diff --git a/src/components/RulesComponents.jsx b/src/components/RulesComponents.tsx similarity index 60% rename from src/components/RulesComponents.jsx rename to src/components/RulesComponents.tsx index 68243ff..87a19bf 100644 --- a/src/components/RulesComponents.jsx +++ b/src/components/RulesComponents.tsx @@ -1,7 +1,12 @@ -// src/components/RulesComponents.jsx +// src/components/RulesComponents.tsx -// Reusable Section Header (e.g., "OVERVIEW OF THE GAME") -export function RuleHeader({ title }) { +import React from 'react'; + +export interface RuleHeaderProps { + title: string; +} + +export function RuleHeader({ title }: RuleHeaderProps) { return ( <>
@@ -12,8 +17,12 @@ export function RuleHeader({ title }) { ); } -// Standard text wrapper -export function DescBox({ children, className = "" }) { +export interface DescBoxProps { + children: React.ReactNode; + className?: string; +} + +export function DescBox({ children, className = "" }: DescBoxProps) { return (
{children} @@ -21,8 +30,12 @@ export function DescBox({ children, className = "" }) { ); } -// Quote formatter -export function Quote({ text, author }) { +export interface QuoteProps { + text: string; + author: string; +} + +export function Quote({ text, author }: QuoteProps) { return (

“{text}” ({author}) @@ -30,8 +43,11 @@ export function Quote({ text, author }) { ); } -// Custom Numbered Setup List -export function FancierListItem({ children }) { +export interface FancierListItemProps { + children: React.ReactNode; +} + +export function FancierListItem({ children }: FancierListItemProps) { return (
  • {children} @@ -39,8 +55,11 @@ export function FancierListItem({ children }) { ); } -// End Game Step Icon -export function StepIcon({ num }) { +export interface StepIconProps { + num: string | number; +} + +export function StepIcon({ num }: StepIconProps) { return ( {num} @@ -48,8 +67,15 @@ export function StepIcon({ num }) { ); } -// Military Conflict Example Column -export function MilitaryChild({ title, img, strength, children, isEven }) { +export interface MilitaryChildProps { + title: string; + img: string; + strength?: string; + children?: React.ReactNode; + isEven?: boolean; +} + +export function MilitaryChild({ title, img, strength, children, isEven }: MilitaryChildProps) { return (

    {title}

    @@ -60,8 +86,12 @@ export function MilitaryChild({ title, img, strength, children, isEven }) { ); } -// Helper for the left-text, right-image split rows -export function FlexRow({ children, className = "" }) { +export interface FlexRowProps { + children: React.ReactNode; + className?: string; +} + +export function FlexRow({ children, className = "" }: FlexRowProps) { return (
    {children} @@ -69,11 +99,17 @@ export function FlexRow({ children, className = "" }) { ); } -// Helper for the Armada Shipyard Naval Tracks -export function NavalTrack({ img, colorClass, title, children }) { +export interface NavalTrackProps { + img: string; + colorClass: string; + title: string; + children: React.ReactNode; +} + +export function NavalTrack({ img, colorClass, title, children }: NavalTrackProps) { return ( -
    - +
    +
    {title}
    {children} @@ -82,17 +118,25 @@ export function NavalTrack({ img, colorClass, title, children }) { ); } -// Helper for filled definition/restriction boxes -export function FilledInfoBox({ children, className = "" }) { +export interface FilledInfoBoxProps { + children: React.ReactNode; + className?: string; +} + +export function FilledInfoBox({ children, className = "" }: FilledInfoBoxProps) { return ( -
    +
    {children}
    ); } -// Updated Science Table to support Armada's extended 9-column layout -export function ScienceTable({ limit = 6, showAsterisk = false }) { +export interface ScienceTableProps { + limit?: number; + showAsterisk?: boolean; +} + +export function ScienceTable({ limit = 6, showAsterisk = false }: ScienceTableProps) { const counts = Array.from({ length: limit }, (_, i) => i + 1); return (
    @@ -100,13 +144,13 @@ export function ScienceTable({ limit = 6, showAsterisk = false }) { Number of identical symbols - {counts.map(c => {c})} - {showAsterisk && ...} + {counts.map(c => {c})} + {showAsterisk && ...} Victory points - {counts.map(c => {c * c})} - {showAsterisk && *} + {counts.map(c => {c * c})} + {showAsterisk && *} diff --git a/src/main.jsx b/src/main.tsx similarity index 64% rename from src/main.jsx rename to src/main.tsx index b9a1a6d..bef5202 100644 --- a/src/main.jsx +++ b/src/main.tsx @@ -1,9 +1,9 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' -import App from './App.jsx' +import App from './App.tsx' -createRoot(document.getElementById('root')).render( +createRoot(document.getElementById('root')!).render( , diff --git a/src/pages/Effects.jsx b/src/pages/Effects.tsx similarity index 99% rename from src/pages/Effects.jsx rename to src/pages/Effects.tsx index ee4b37f..1e1bd7c 100644 --- a/src/pages/Effects.jsx +++ b/src/pages/Effects.tsx @@ -1,7 +1,7 @@ -// src/pages/Effects.jsx +// src/pages/Effects.tsx +import { Clarification, InfoBox, Note, SectionContainer, SectionDivider, SectionHeader, SideEffect, SpecialDescription, TopEffect } from '../components/EffectComponents'; import Navbar from '../components/Navbar'; -import { SideEffect, TopEffect, InfoBox, SpecialDescription, SectionHeader, SectionDivider, SectionContainer, Clarification, Note } from '../components/EffectComponents'; export default function Effects() { return ( diff --git a/src/pages/Home.jsx b/src/pages/Home.tsx similarity index 100% rename from src/pages/Home.jsx rename to src/pages/Home.tsx diff --git a/src/pages/Rules.jsx b/src/pages/Rules.tsx similarity index 87% rename from src/pages/Rules.jsx rename to src/pages/Rules.tsx index d3054b2..250bbde 100644 --- a/src/pages/Rules.jsx +++ b/src/pages/Rules.tsx @@ -1,15 +1,23 @@ -// src/pages/Rules.jsx +// src/pages/Rules.tsx -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; +import { InfoBox, SectionContainer, SectionDivider, SectionHeader } from '../components/EffectComponents'; import Navbar from '../components/Navbar'; -import { SectionContainer, SectionHeader, InfoBox, SectionDivider } from '../components/EffectComponents'; import { - RuleHeader, DescBox, Quote, FancierListItem, StepIcon, MilitaryChild, - ScienceTable, FlexRow, NavalTrack, FilledInfoBox + DescBox, + FancierListItem, + FilledInfoBox, + FlexRow, + MilitaryChild, + NavalTrack, + Quote, + RuleHeader, + ScienceTable, + StepIcon } from '../components/RulesComponents'; export default function Rules() { - const [showTopBtn, setShowTopBtn] = useState(false); + const [showTopBtn, setShowTopBtn] = useState(false); useEffect(() => { const handleScroll = () => { @@ -134,7 +142,7 @@ export default function Rules() {

    There are four types of Military Conflict tokens:

    -
    +
    • Military Defeat -1 tokens
    • Military Defeat +1 tokens
    • @@ -142,21 +150,21 @@ export default function Rules() {
    • Military Defeat +5 tokens
    - - - - + + + +
    -
    +

    COINS

    Coins represent your Treasure. They are worth 1 or 3.

    - - + +
    @@ -200,7 +208,7 @@ export default function Rules() {

    A game plays over three successive rounds: Age I, Age II, then Age III. Each round plays in a similar fashion. Victory points are scored at the end of Age III.

    -
    +

    Definitions:

    Neighbors: The player directly on your left and the player directly on your right.

    City: Your developments, which includes your Wonder and constructed cards.

    @@ -251,24 +259,24 @@ export default function Rules() {
  • Higher: Take the Victory token for the current Age (+1, +3, or +5).
  • -
    +
    {[{ img: 'military_-1', text: 'Defeat' }, { img: 'military_1', text: 'Age I' }, { img: 'military_3', text: 'Age II' }, { img: 'military_5', text: 'Age III' }].map(item => (
    - +

    {item.text}

    ))}
    -
    +
    - +

    Defeat

    - +

    Victory

    @@ -300,7 +308,7 @@ export default function Rules() {

    D. FREE COST THROUGH CHAINS

    If you have previously constructed a card with the same symbol in the upper right corner, you can construct the new card for free.

    - + @@ -347,7 +355,7 @@ export default function Rules() { -
    +
    @@ -362,7 +370,7 @@ export default function Rules() { -
      +
      • 1 Wonder board (Siracusa)
      • 7 Shipyard boards
      • 28 Fleet figures (7 x 4 colors)
      • @@ -392,11 +400,11 @@ export default function Rules() {

        WONDER BOARDS

        Siracusa is only used if playing with this expansion.

        - +

        SHIPYARD BOARDS

        Shipyard boards represent the progress of your Fleets during the game. Shipyards have 4 naval tracks (Red, Yellow, Blue, and Green) each divided into 7 spaces, most of which provide an effect. Each Shipyard board has a Wonder symbol on one of the naval tracks. Its position is different on each board.

        - +

        Note: The effects of spaces are identical on each Shipyard board. Only the construction costs and placement of the Wonder symbol are different between boards.

        @@ -407,10 +415,10 @@ export default function Rules() {

        Figures track the progress of your Fleets (Red, Yellow, Blue, and Green) on your Shipyard board.

    - - - - + + + +
    @@ -421,10 +429,10 @@ export default function Rules() {

    ARMADA AGE CARDS

    Age cards in this expansion represent new Buildings with never before seen effects. They are added to the Age cards from the base game.

    -

    Note: To help with sorting, Age cards from the Armada expansion all have this symbol in the bottom-left corner.

    +

    Note: To help with sorting, Age cards from the Armada expansion all have this symbol in the bottom-left corner.

    - +
    @@ -437,20 +445,20 @@ export default function Rules() {

    Front

    - +

    Island

    - +

    Level 1

    Back

    - +

    Level 2

    - +

    Level 3

    @@ -469,15 +477,15 @@ export default function Rules() {
    - - - + + +
    - - - - + + + +
    @@ -490,7 +498,7 @@ export default function Rules() {

    Boarding tokens represent the new Boarding effect.

    - +
    @@ -559,8 +567,8 @@ export default function Rules() {

    To do this, you must pay, at the same time, the cost of the card and the cost of the Naval Construction. Then move the Fleet figure of this color forward one space on your Shipyard board.

    -

    Example: You construct a Blue card and can now undertake a Blue Naval Construction. The card costs 1 Stone and the Blue Naval Construction costs 1 Clay. You must therefore pay a total of 1 Stone and 1 Clay to complete both constructions.

    - +

    Example: You construct a Blue card and can now undertake a Blue Naval Construction. The card costs 1 Stone and the Blue Naval Construction costs 1 Clay. You must therefore pay a total of 1 Stone and 1 Clay to complete both constructions.

    +

    Important: Constructing a Building (or a stage of a Wonder) and Naval Construction are always paid simultaneously. However, you always apply the effects of cards (or stages of a Wonder) before the effects of Naval Constructions.

    When you construct a Red, Yellow, Blue, or Green card for free due to chains (or from Olympía), you can also undertake a Naval Construction by paying its cost following the previously described rules.

    @@ -574,8 +582,8 @@ export default function Rules() {

    To do this, you must pay, at the same time, the cost of your Wonder stage and the cost of the Naval Construction. Then advance the Fleet figure associated with your Wonder by one space on your Shipyard board.

    -

    Example: You construct the first stage of your Wonder, which costs 2 Stones. You can then undertake a Naval Construction to advance your Red Fleet (associated with your Wonder), which costs 1 Wood. You must therefore pay a total of 2 Stones and 1 Wood to complete both constructions.

    - +

    Example: You construct the first stage of your Wonder, which costs 2 Stones. You can then undertake a Naval Construction to advance your Red Fleet (associated with your Wonder), which costs 1 Wood. You must therefore pay a total of 2 Stones and 1 Wood to complete both constructions.

    +
    Description of the Shipyard boards
    @@ -584,8 +592,8 @@ export default function Rules() {

    To see out the naval strength provided by your Red Fleet, look at the value shown by the naval symbol on the space the figure is on.

    -

    Example: Your Red Fleet is on the third space of your Red naval track. Your naval strength is increased by 2.

    - +

    Example: Your Red Fleet is on the third space of your Red naval track. Your naval strength is increased by 2.

    +

    The Yellow naval track shows your commercial level. It also grants Coins and raises Taxes that impact other players.

    @@ -597,8 +605,8 @@ export default function Rules() {

    TAX

    When your Yellow Fleet reaches a Tax space , you raise Taxes at the end of the turn. If this happens, all players except you lose the number of Coins indicated minus their respective commercial level. Coins are put back in the reserve.

    -

    Example: A player reaches the fifth space of the Yellow naval track with their Yellow Fleet; they raise Taxes by 2 at the end of the turn. Your Yellow Fleet is on the second space of the Yellow naval track; you therefore lose 1 Coin (Tax of 2 minus your commercial level of 1).

    - +

    Example: A player reaches the fifth space of the Yellow naval track with their Yellow Fleet; they raise Taxes by 2 at the end of the turn. Your Yellow Fleet is on the second space of the Yellow naval track; you therefore lose 1 Coin (Tax of 2 minus your commercial level of 1).

    +

    Clarifications: