From 3933415b2d9f9023a1e8504b5875777b3477dc44 Mon Sep 17 00:00:00 2001 From: Reynier Matthieu Date: Thu, 27 Feb 2025 16:56:23 +0100 Subject: [PATCH] Initial commit from your app --- package-lock.json | 496 +++++++-------- src/components/CardSearch.tsx | 1049 ++++++++++++++++---------------- src/components/DeckManager.tsx | 690 ++++++++++++--------- src/components/MagicCard.tsx | 31 + src/utils/deckValidation.ts | 5 +- 5 files changed, 1220 insertions(+), 1051 deletions(-) create mode 100644 src/components/MagicCard.tsx diff --git a/package-lock.json b/package-lock.json index 8d2337b..cf40f03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,30 +70,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", - "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", - "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", + "@babel/generator": "^7.26.9", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.7", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.26.7", - "@babel/types": "^7.26.7", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -109,13 +109,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", - "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", "dev": true, "dependencies": { - "@babel/parser": "^7.26.5", - "@babel/types": "^7.26.5", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -207,25 +207,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", + "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", "dev": true, "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", - "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "dev": true, "dependencies": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.26.9" }, "bin": { "parser": "bin/babel-parser.js" @@ -265,30 +265,30 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", - "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.7", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -306,9 +306,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -740,9 +740,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.15" @@ -752,9 +752,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -787,9 +787,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", - "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -805,12 +805,12 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "dev": true, "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.12.0", "levn": "^0.4.1" }, "engines": { @@ -866,9 +866,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, "engines": { "node": ">=18.18" @@ -989,9 +989,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.1.tgz", - "integrity": "sha512-kwctwVlswSEsr4ljpmxKrRKp1eG1v2NAhlzFzDf1x1OdYaMjBYjDCbHkzWm57ZXzTwqn8stMXgROrnMw8dJK3w==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", "cpu": [ "arm" ], @@ -1002,9 +1002,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.1.tgz", - "integrity": "sha512-4H5ZtZitBPlbPsTv6HBB8zh1g5d0T8TzCmpndQdqq20Ugle/nroOyDMf9p7f88Gsu8vBLU78/cuh8FYHZqdXxw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", "cpu": [ "arm64" ], @@ -1015,9 +1015,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.1.tgz", - "integrity": "sha512-f2AJ7Qwx9z25hikXvg+asco8Sfuc5NCLg8rmqQBIOUoWys5sb/ZX9RkMZDPdnnDevXAMJA5AWLnRBmgdXGEUiA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", "cpu": [ "arm64" ], @@ -1028,9 +1028,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.1.tgz", - "integrity": "sha512-+/2JBrRfISCsWE4aEFXxd+7k9nWGXA8+wh7ZUHn/u8UDXOU9LN+QYKKhd57sIn6WRcorOnlqPMYFIwie/OHXWw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", "cpu": [ "x64" ], @@ -1041,9 +1041,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.1.tgz", - "integrity": "sha512-SUeB0pYjIXwT2vfAMQ7E4ERPq9VGRrPR7Z+S4AMssah5EHIilYqjWQoTn5dkDtuIJUSTs8H+C9dwoEcg3b0sCA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", "cpu": [ "arm64" ], @@ -1054,9 +1054,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.1.tgz", - "integrity": "sha512-L3T66wAZiB/ooiPbxz0s6JEX6Sr2+HfgPSK+LMuZkaGZFAFCQAHiP3dbyqovYdNaiUXcl9TlgnIbcsIicAnOZg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", "cpu": [ "x64" ], @@ -1067,9 +1067,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.1.tgz", - "integrity": "sha512-UBXdQ4+ATARuFgsFrQ+tAsKvBi/Hly99aSVdeCUiHV9dRTTpMU7OrM3WXGys1l40wKVNiOl0QYY6cZQJ2xhKlQ==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", "cpu": [ "arm" ], @@ -1080,9 +1080,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.1.tgz", - "integrity": "sha512-m/yfZ25HGdcCSwmopEJm00GP7xAUyVcBPjttGLRAqZ60X/bB4Qn6gP7XTwCIU6bITeKmIhhwZ4AMh2XLro+4+w==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", "cpu": [ "arm" ], @@ -1093,9 +1093,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.1.tgz", - "integrity": "sha512-Wy+cUmFuvziNL9qWRRzboNprqSQ/n38orbjRvd6byYWridp5TJ3CD+0+HUsbcWVSNz9bxkDUkyASGP0zS7GAvg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", "cpu": [ "arm64" ], @@ -1106,9 +1106,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.1.tgz", - "integrity": "sha512-CQ3MAGgiFmQW5XJX5W3wnxOBxKwFlUAgSXFA2SwgVRjrIiVt5LHfcQLeNSHKq5OEZwv+VCBwlD1+YKCjDG8cpg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", "cpu": [ "arm64" ], @@ -1119,9 +1119,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.1.tgz", - "integrity": "sha512-rSzb1TsY4lSwH811cYC3OC2O2mzNMhM13vcnA7/0T6Mtreqr3/qs6WMDriMRs8yvHDI54qxHgOk8EV5YRAHFbw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", "cpu": [ "loong64" ], @@ -1132,9 +1132,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.1.tgz", - "integrity": "sha512-fwr0n6NS0pG3QxxlqVYpfiY64Fd1Dqd8Cecje4ILAV01ROMp4aEdCj5ssHjRY3UwU7RJmeWd5fi89DBqMaTawg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", "cpu": [ "ppc64" ], @@ -1145,9 +1145,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.1.tgz", - "integrity": "sha512-4uJb9qz7+Z/yUp5RPxDGGGUcoh0PnKF33QyWgEZ3X/GocpWb6Mb+skDh59FEt5d8+Skxqs9mng6Swa6B2AmQZg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", "cpu": [ "riscv64" ], @@ -1158,9 +1158,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.1.tgz", - "integrity": "sha512-QlIo8ndocWBEnfmkYqj8vVtIUpIqJjfqKggjy7IdUncnt8BGixte1wDON7NJEvLg3Kzvqxtbo8tk+U1acYEBlw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", "cpu": [ "s390x" ], @@ -1171,9 +1171,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.1.tgz", - "integrity": "sha512-hzpleiKtq14GWjz3ahWvJXgU1DQC9DteiwcsY4HgqUJUGxZThlL66MotdUEK9zEo0PK/2ADeZGM9LIondE302A==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", "cpu": [ "x64" ], @@ -1184,9 +1184,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.1.tgz", - "integrity": "sha512-jqtKrO715hDlvUcEsPn55tZt2TEiBvBtCMkUuU0R6fO/WPT7lO9AONjPbd8II7/asSiNVQHCMn4OLGigSuxVQA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", "cpu": [ "x64" ], @@ -1197,9 +1197,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.1.tgz", - "integrity": "sha512-RnHy7yFf2Wz8Jj1+h8klB93N0NHNHXFhNwAmiy9zJdpY7DE01VbEVtPdrK1kkILeIbHGRJjvfBDBhnxBr8kD4g==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", "cpu": [ "arm64" ], @@ -1210,9 +1210,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.1.tgz", - "integrity": "sha512-i7aT5HdiZIcd7quhzvwQ2oAuX7zPYrYfkrd1QFfs28Po/i0q6kas/oRrzGlDhAEyug+1UfUtkWdmoVlLJj5x9Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", "cpu": [ "ia32" ], @@ -1223,9 +1223,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.1.tgz", - "integrity": "sha512-k3MVFD9Oq+laHkw2N2v7ILgoa9017ZMF/inTtHzyTVZjYs9cSH18sdyAf6spBAJIGwJ5UaC7et2ZH1WCdlhkMw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", "cpu": [ "x64" ], @@ -1236,9 +1236,9 @@ ] }, "node_modules/@supabase/auth-js": { - "version": "2.67.3", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.67.3.tgz", - "integrity": "sha512-NJDaW8yXs49xMvWVOkSIr8j46jf+tYHV0wHhrwOaLLMZSFO4g6kKAf+MfzQ2RaD06OCUkUHIzctLAxjTgEVpzw==", + "version": "2.68.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.68.0.tgz", + "integrity": "sha512-odG7nb7aOmZPUXk6SwL2JchSsn36Ppx11i2yWMIc/meUO2B2HK9YwZHPK06utD9Ql9ke7JKDbwGin/8prHKxxQ==", "dependencies": { "@supabase/node-fetch": "^2.6.14" } @@ -1263,9 +1263,9 @@ } }, "node_modules/@supabase/postgrest-js": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.18.1.tgz", - "integrity": "sha512-dWDnoC0MoDHKhaEOrsEKTadWQcBNknZVQcSgNE/Q2wXh05mhCL1ut/jthRUrSbYcqIw/CEjhaeIPp7dLarT0bg==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.2.tgz", + "integrity": "sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==", "dependencies": { "@supabase/node-fetch": "^2.6.14" } @@ -1290,14 +1290,14 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.48.1.tgz", - "integrity": "sha512-VMD+CYk/KxfwGbI4fqwSUVA7CLr1izXpqfFerhnYPSi6LEKD8GoR4kuO5Cc8a+N43LnfSQwLJu4kVm2e4etEmA==", + "version": "2.49.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.1.tgz", + "integrity": "sha512-lKaptKQB5/juEF5+jzmBeZlz69MdHZuxf+0f50NwhL+IE//m4ZnOeWlsKRjjsM0fVayZiQKqLvYdBn0RLkhGiQ==", "dependencies": { - "@supabase/auth-js": "2.67.3", + "@supabase/auth-js": "2.68.0", "@supabase/functions-js": "2.4.4", "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "1.18.1", + "@supabase/postgrest-js": "1.19.2", "@supabase/realtime-js": "2.11.2", "@supabase/storage-js": "2.7.1" } @@ -1356,9 +1356,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz", - "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==", + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", "dependencies": { "undici-types": "~6.20.0" } @@ -1402,20 +1402,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", - "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", + "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/type-utils": "8.22.0", - "@typescript-eslint/utils": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/type-utils": "8.25.0", + "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1431,15 +1431,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", - "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", + "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/typescript-estree": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4" }, "engines": { @@ -1455,13 +1455,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", - "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", + "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0" + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1472,15 +1472,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", - "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", + "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.22.0", - "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/typescript-estree": "8.25.0", + "@typescript-eslint/utils": "8.25.0", "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1495,9 +1495,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", - "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", + "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1508,19 +1508,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", - "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", + "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/visitor-keys": "8.22.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1558,9 +1558,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -1570,15 +1570,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", - "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", + "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.22.0", - "@typescript-eslint/types": "8.22.0", - "@typescript-eslint/typescript-estree": "8.22.0" + "@typescript-eslint/scope-manager": "8.25.0", + "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/typescript-estree": "8.25.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1593,12 +1593,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", - "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", + "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/types": "8.25.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1851,9 +1851,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001696", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", - "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "version": "1.0.30001701", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz", + "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==", "dev": true, "funding": [ { @@ -2035,9 +2035,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.90", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", - "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", + "version": "1.5.107", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.107.tgz", + "integrity": "sha512-dJr1o6yCntRkXElnhsHh1bAV19bo/hKyFf7tCcWgpXbuFIF0Lakjgqv5LRfSDaNzAII8Fnxg2tqgHkgCvxdbxw==", "dev": true }, "node_modules/emoji-regex": { @@ -2106,21 +2106,21 @@ } }, "node_modules/eslint": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", - "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -2177,9 +2177,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz", - "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==", + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", + "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", "dev": true, "peerDependencies": { "eslint": ">=8.40" @@ -2319,9 +2319,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2381,18 +2381,18 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -2504,9 +2504,9 @@ } }, "node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, "engines": { "node": ">=18" @@ -3094,9 +3094,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -3357,9 +3357,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "engines": { "iojs": ">=1.0.0", @@ -3367,9 +3367,9 @@ } }, "node_modules/rollup": { - "version": "4.34.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.1.tgz", - "integrity": "sha512-iYZ/+PcdLYSGfH3S+dGahlW/RWmsqDhLgj1BT9DH/xXJ0ggZN7xkdP9wipPNjjNLczI+fmMLmTB9pye+d2r4GQ==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", "dev": true, "dependencies": { "@types/estree": "1.0.6" @@ -3382,25 +3382,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.1", - "@rollup/rollup-android-arm64": "4.34.1", - "@rollup/rollup-darwin-arm64": "4.34.1", - "@rollup/rollup-darwin-x64": "4.34.1", - "@rollup/rollup-freebsd-arm64": "4.34.1", - "@rollup/rollup-freebsd-x64": "4.34.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.1", - "@rollup/rollup-linux-arm-musleabihf": "4.34.1", - "@rollup/rollup-linux-arm64-gnu": "4.34.1", - "@rollup/rollup-linux-arm64-musl": "4.34.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.1", - "@rollup/rollup-linux-riscv64-gnu": "4.34.1", - "@rollup/rollup-linux-s390x-gnu": "4.34.1", - "@rollup/rollup-linux-x64-gnu": "4.34.1", - "@rollup/rollup-linux-x64-musl": "4.34.1", - "@rollup/rollup-win32-arm64-msvc": "4.34.1", - "@rollup/rollup-win32-ia32-msvc": "4.34.1", - "@rollup/rollup-win32-x64-msvc": "4.34.1", + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" } }, @@ -3759,14 +3759,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.22.0.tgz", - "integrity": "sha512-Y2rj210FW1Wb6TWXzQc5+P+EWI9/zdS57hLEc0gnyuvdzWo8+Y8brKlbj0muejonhMI/xAZCnZZwjbIfv1CkOw==", + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.25.0.tgz", + "integrity": "sha512-TxRdQQLH4g7JkoFlYG3caW5v1S6kEkz8rqt80iQJZUYPq1zD1Ra7HfQBJJ88ABRaMvHAXnwRvRB4V+6sQ9xN5Q==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.22.0", - "@typescript-eslint/parser": "8.22.0", - "@typescript-eslint/utils": "8.22.0" + "@typescript-eslint/eslint-plugin": "8.25.0", + "@typescript-eslint/parser": "8.25.0", + "@typescript-eslint/utils": "8.25.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3786,9 +3786,9 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -4016,9 +4016,9 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "engines": { "node": ">=10.0.0" }, diff --git a/src/components/CardSearch.tsx b/src/components/CardSearch.tsx index 08a306c..fd8c3a4 100644 --- a/src/components/CardSearch.tsx +++ b/src/components/CardSearch.tsx @@ -1,548 +1,543 @@ import React, { useState } from 'react'; - import { searchCards } from '../services/api'; - import { Card } from '../types'; +import { searchCards } from '../services/api'; +import { Card } from '../types'; +import MagicCard from './MagicCard'; - const CardSearch = () => { - const [cardName, setCardName] = useState(''); - const [text, setText] = useState(''); - const [rulesText, setRulesText] = useState(''); - const [typeLine, setTypeLine] = useState(''); - const [typeMatch, setTypeMatch] = useState('partial'); - const [typeInclude, setTypeInclude] = useState(true); - const [colors, setColors] = useState({ W: false, U: false, B: false, R: false, G: false, C: false }); - const [colorMode, setColorMode] = useState('exactly'); - const [commanderColors, setCommanderColors] = useState({ W: false, U: false, B: false, R: false, G: false, C: false }); - const [manaCost, setManaCost] = useState({ W: 0, U: 0, B: 0, R: 0, G: 0, C: 0 }); - const [manaValue, setManaValue] = useState(''); - const [manaValueComparison, setManaValueComparison] = useState('='); - const [games, setGames] = useState({ paper: false, arena: false, mtgo: false }); - const [format, setFormat] = useState(''); - const [formatStatus, setFormatStatus] = useState(''); - const [set, setSet] = useState(''); - const [block, setBlock] = useState(''); - const [rarity, setRarity] = useState({ common: false, uncommon: false, rare: false, mythic: false }); - const [criteria, setCriteria] = useState(''); - const [criteriaMatch, setCriteriaMatch] = useState('partial'); - const [criteriaInclude, setCriteriaInclude] = useState(true); - const [price, setPrice] = useState(''); - const [currency, setCurrency] = useState('usd'); - const [priceComparison, setPriceComparison] = useState('='); - const [artist, setArtist] = useState(''); - const [flavorText, setFlavorText] = useState(''); - const [loreFinder, setLoreFinder] = useState(''); - const [language, setLanguage] = useState('en'); - const [displayImages, setDisplayImages] = useState(false); - const [order, setOrder] = useState('name'); - const [showAllPrints, setShowAllPrints] = useState(false); - const [includeExtras, setIncludeExtras] = useState(false); - const [searchResults, setSearchResults] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); +const CardSearch = () => { + const [cardName, setCardName] = useState(''); + const [text, setText] = useState(''); + const [rulesText, setRulesText] = useState(''); + const [typeLine, setTypeLine] = useState(''); + const [typeMatch, setTypeMatch] = useState('partial'); + const [typeInclude, setTypeInclude] = useState(true); + const [colors, setColors] = useState({ W: false, U: false, B: false, R: false, G: false, C: false }); + const [colorMode, setColorMode] = useState('exactly'); + const [commanderColors, setCommanderColors] = useState({ W: false, U: false, B: false, R: false, G: false, C: false }); + const [manaCost, setManaCost] = useState({ W: 0, U: 0, B: 0, R: 0, G: 0, C: 0 }); + const [manaValue, setManaValue] = useState(''); + const [manaValueComparison, setManaValueComparison] = useState('='); + const [games, setGames] = useState({ paper: false, arena: false, mtgo: false }); + const [format, setFormat] = useState(''); + const [formatStatus, setFormatStatus] = useState(''); + const [set, setSet] = useState(''); + const [block, setBlock] = useState(''); + const [rarity, setRarity] = useState({ common: false, uncommon: false, rare: false, mythic: false }); + const [criteria, setCriteria] = useState(''); + const [criteriaMatch, setCriteriaMatch] = useState('partial'); + const [criteriaInclude, setCriteriaInclude] = useState(true); + const [price, setPrice] = useState(''); + const [currency, setCurrency] = useState('usd'); + const [priceComparison, setPriceComparison] = useState('='); + const [artist, setArtist] = useState(''); + const [flavorText, setFlavorText] = useState(''); + const [loreFinder, setLoreFinder] = useState(''); + const [language, setLanguage] = useState('en'); + const [displayImages, setDisplayImages] = useState(false); + const [order, setOrder] = useState('name'); + const [showAllPrints, setShowAllPrints] = useState(false); + const [includeExtras, setIncludeExtras] = useState(false); + const [searchResults, setSearchResults] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); - const handleSearch = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - setError(null); + const handleSearch = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); - let query = ''; + let query = ''; - if (cardName) query += `name:${cardName} `; - if (text) query += `o:${text} `; - if (rulesText) query += `o:"${rulesText.replace('~', cardName)}" `; - if (typeLine) { - const typeQuery = typeMatch === 'partial' ? typeLine : `"${typeLine}"`; - query += `${typeInclude ? '' : '-'}t:${typeQuery} `; - } - if (Object.values(colors).some(Boolean)) { - const activeColors = Object.keys(colors).filter((key) => colors[key as keyof typeof colors]).join(''); - const colorQuery = colorMode === 'exactly' ? `c:${activeColors}` : `color<=${activeColors}`; - query += `${colorQuery} `; - } - if (Object.values(commanderColors).some(Boolean)) { - const activeColors = Object.keys(commanderColors).filter((key) => commanderColors[key as keyof typeof commanderColors]).join(''); - query += `id:${activeColors} `; - } + if (cardName) query += `name:${cardName} `; + if (text) query += `o:${text} `; + if (rulesText) query += `o:"${rulesText.replace('~', cardName)}" `; + if (typeLine) { + const typeQuery = typeMatch === 'partial' ? typeLine : `"${typeLine}"`; + query += `${typeInclude ? '' : '-'}t:${typeQuery} `; + } + if (Object.values(colors).some(Boolean)) { + const activeColors = Object.keys(colors).filter((key) => colors[key as keyof typeof colors]).join(''); + const colorQuery = colorMode === 'exactly' ? `c:${activeColors}` : `color<=${activeColors}`; + query += `${colorQuery} `; + } + if (Object.values(commanderColors).some(Boolean)) { + const activeColors = Object.keys(commanderColors).filter((key) => commanderColors[key as keyof typeof commanderColors]).join(''); + query += `id:${activeColors} `; + } - const manaCostString = Object.entries(manaCost) - .filter(([, count]) => count > 0) - .map(([color, count]) => `{${color}}`.repeat(count)) - .join(''); + const manaCostString = Object.entries(manaCost) + .filter(([, count]) => count > 0) + .map(([color, count]) => `{${color}}`.repeat(count)) + .join(''); - if (manaCostString) query += `m:${manaCostString} `; + if (manaCostString) query += `m:${manaCostString} `; - if (manaValue) query += `mv${manaValueComparison}${manaValue} `; - if (Object.values(games).some(Boolean)) { - const activeGames = Object.keys(games).filter((key) => games[key as keyof typeof games]).join(','); - query += `game:${activeGames} `; - } - if (format) query += `f:${format} `; - if (formatStatus) query += `${formatStatus}:${format} `; - if (set) query += `e:${set} `; - if (block) query += `b:${block} `; - if (Object.values(rarity).some(Boolean)) { - const activeRarities = Object.keys(rarity).filter((key) => rarity[key as keyof typeof rarity]).join(','); - query += `r:${activeRarities} `; - } - if (criteria) { - const criteriaQuery = criteriaMatch === 'partial' ? criteria : `"${criteria}"`; - query += `${criteriaInclude ? '' : '-'}o:${criteriaQuery} `; - } - if (price) query += `${currency}${priceComparison}${price} `; - if (artist) query += `a:${artist} `; - if (flavorText) query += `ft:${flavorText} `; - if (loreFinder) query += `${loreFinder} `; - if (language) query += `lang:${language} `; - if (displayImages) query += `display:grid `; - if (order) query += `order:${order} `; - if (showAllPrints) query += `unique:prints `; - if (includeExtras) query += `include:extras `; + if (manaValue) query += `mv${manaValueComparison}${manaValue} `; + if (Object.values(games).some(Boolean)) { + const activeGames = Object.keys(games).filter((key) => games[key as keyof typeof games]).join(','); + query += `game:${activeGames} `; + } + if (format) query += `f:${format} `; + if (formatStatus) query += `${formatStatus}:${format} `; + if (set) query += `e:${set} `; + if (block) query += `b:${block} `; + if (Object.values(rarity).some(Boolean)) { + const activeRarities = Object.keys(rarity).filter((key) => rarity[key as keyof typeof rarity]).join(','); + query += `r:${activeRarities} `; + } + if (criteria) { + const criteriaQuery = criteriaMatch === 'partial' ? criteria : `"${criteria}"`; + query += `${criteriaInclude ? '' : '-'}o:${criteriaQuery} `; + } + if (price) query += `${currency}${priceComparison}${price} `; + if (artist) query += `a:${artist} `; + if (flavorText) query += `ft:${flavorText} `; + if (loreFinder) query += `${loreFinder} `; + if (language) query += `lang:${language} `; + if (displayImages) query += `display:grid `; + if (order) query += `order:${order} `; + if (showAllPrints) query += `unique:prints `; + if (includeExtras) query += `include:extras `; - try { - const cards = await searchCards(query.trim()); - setSearchResults(cards || []); - } catch (err) { - setError('Failed to fetch cards.'); - console.error('Error fetching cards:', err); - } finally { - setLoading(false); - } - }; + try { + const cards = await searchCards(query.trim()); + setSearchResults(cards || []); + } catch (err) { + setError('Failed to fetch cards.'); + console.error('Error fetching cards:', err); + } finally { + setLoading(false); + } + }; - return ( -
-
-

Card Search

-
- {/* Card Details */} -
- setCardName(e.target.value)} - className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Card Name" - /> - setText(e.target.value)} - className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Text" - /> - setRulesText(e.target.value)} - className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Rules Text (~ for card name)" - /> -
- setTypeLine(e.target.value)} - className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Type Line" - /> - - -
-
+ return ( +
+
+

Card Search

+ + {/* Card Details */} +
+ setCardName(e.target.value)} + className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Card Name" + /> + setText(e.target.value)} + className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Text" + /> + setRulesText(e.target.value)} + className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Rules Text (~ for card name)" + /> +
+ setTypeLine(e.target.value)} + className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Type Line" + /> + + +
+
- {/* Colors */} -
-
-

Card Colors

-
- {Object.entries(colors).map(([color, active]) => ( - - ))} -
- -
-
-

Commander Colors

-
- {Object.entries(commanderColors).map(([color, active]) => ( - - ))} -
-
-
- - {/* Mana Cost */} -
- {Object.entries(manaCost).map(([color, count]) => ( -
+ {/* Colors */} +
+
+

Card Colors

+
+ {Object.entries(colors).map(([color, active]) => ( +
+ ))}
- - {/* Stats */} -
- - setManaValue(e.target.value)} - className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Mana Value" - /> -
- - {/* Games */} -
-

Games

-
- {['paper', 'arena', 'mtgo'].map((game) => ( - - ))} -
-
- - {/* Formats */} -
- - -
- - {/* Sets */} -
- setSet(e.target.value)} - className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Set Code" - /> - setBlock(e.target.value)} - className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Block Code" - /> -
- - {/* Rarity */} -
-

Rarity

-
- {['common', 'uncommon', 'rare', 'mythic'].map((r) => ( - - ))} -
-
- - {/* Criteria */} -
- setCriteria(e.target.value)} - className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Criteria" - /> - - -
- - {/* Prices */} -
- - - setPrice(e.target.value)} - className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Price" - /> -
- - {/* Additional Filters */} -
- setArtist(e.target.value)} - className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Artist" - /> - setFlavorText(e.target.value)} - className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Flavor Text" - /> - setLoreFinder(e.target.value)} - className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Lore Finderβ„’" - /> - -
- - {/* Preferences */} -
- - - - -
- - - - - {loading && ( -
-
-
- )} - - {error && ( -
- {error} -
- )} - - {searchResults && searchResults.length > 0 && ( -
- {searchResults.map((card) => ( -
- {card.image_uris?.normal && ( - {card.name} - )} -
-

{card.name}

-

{card.type_line}

-
-
+ + + +
+
+

Commander Colors

+
+ {Object.entries(commanderColors).map(([color, active]) => ( + ))}
- )} +
-
- ); - }; - export default CardSearch; + {/* Mana Cost */} +
+ {Object.entries(manaCost).map(([color, count]) => ( +
+ + {color === 'W' ? 'βšͺ' : color === 'U' ? 'πŸ”΅' : color === 'B' ? '⚫' : color === 'R' ? 'πŸ”΄' : color === 'G' ? '🟒' : '🟀'} + + setManaCost({ ...manaCost, [color]: parseInt(e.target.value) })} + className="w-16 px-2 py-1 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + min="0" + /> +
+ ))} +
+ + {/* Stats */} +
+ + setManaValue(e.target.value)} + className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Mana Value" + /> +
+ + {/* Games */} +
+

Games

+
+ {['paper', 'arena', 'mtgo'].map((game) => ( + + ))} +
+
+ + {/* Formats */} +
+ + +
+ + {/* Sets */} +
+ setSet(e.target.value)} + className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Set Code" + /> + setBlock(e.target.value)} + className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Block Code" + /> +
+ + {/* Rarity */} +
+

Rarity

+
+ {['common', 'uncommon', 'rare', 'mythic'].map((r) => ( + + ))} +
+
+ + {/* Criteria */} +
+ setCriteria(e.target.value)} + className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Criteria" + /> + + +
+ + {/* Prices */} +
+ + + setPrice(e.target.value)} + className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Price" + /> +
+ + {/* Additional Filters */} +
+ setArtist(e.target.value)} + className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Artist" + /> + setFlavorText(e.target.value)} + className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Flavor Text" + /> + setLoreFinder(e.target.value)} + className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Lore Finderβ„’" + /> + +
+ + {/* Preferences */} +
+ + + + +
+ + + + + {loading && ( +
+
+
+ )} + + {error && ( +
+ {error} +
+ )} + + {searchResults && searchResults.length > 0 && ( +
+ {searchResults.map((card) => ( +
+ +
+

{card.name}

+

{card.type_line}

+
+
+ ))} +
+ )} +
+
+ ); +}; + +export default CardSearch; diff --git a/src/components/DeckManager.tsx b/src/components/DeckManager.tsx index 9d923e1..1ada387 100644 --- a/src/components/DeckManager.tsx +++ b/src/components/DeckManager.tsx @@ -1,276 +1,416 @@ -import React, { useState, useEffect } from 'react'; -import { Plus, Search, Save, Trash2 } from 'lucide-react'; -import { Card, Deck } from '../types'; -import { searchCards } from '../services/api'; -import { useAuth } from '../contexts/AuthContext'; -import { supabase } from '../lib/supabase'; -import { validateDeck } from '../utils/deckValidation'; - -interface DeckManagerProps { - initialDeck?: Deck; - onSave?: () => void; -} - -export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) { - const [searchQuery, setSearchQuery] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const [selectedCards, setSelectedCards] = useState<{ card: Card; quantity: number }[]>( - initialDeck?.cards || [] - ); - const [deckName, setDeckName] = useState(initialDeck?.name || ''); - const [deckFormat, setDeckFormat] = useState(initialDeck?.format || 'standard'); - const { user } = useAuth(); - - const handleSearch = async (e: React.FormEvent) => { - e.preventDefault(); - if (!searchQuery.trim()) return; - - try { - const cards = await searchCards(searchQuery); - setSearchResults(cards); - } catch (error) { - console.error('Failed to search cards:', error); - } - }; - - const addCardToDeck = (card: Card) => { - setSelectedCards(prev => { - const existing = prev.find(c => c.card.id === card.id); - if (existing) { - return prev.map(c => - c.card.id === card.id - ? { ...c, quantity: Math.min(c.quantity + 1, 4) } - : c - ); - } - return [...prev, { card, quantity: 1 }]; - }); - }; - - const removeCardFromDeck = (cardId: string) => { - setSelectedCards(prev => prev.filter(c => c.card.id !== cardId)); - }; - - const updateCardQuantity = (cardId: string, quantity: number) => { - setSelectedCards(prev => - prev.map(c => - c.card.id === cardId - ? { ...c, quantity: Math.max(1, Math.min(quantity, 4)) } - : c - ) - ); - }; - - const saveDeck = async () => { - if (!deckName.trim() || selectedCards.length === 0 || !user) return; - - const deckToSave: Deck = { - id: initialDeck?.id || crypto.randomUUID(), - name: deckName, - format: deckFormat, - cards: selectedCards, - userId: user.id, - createdAt: initialDeck?.createdAt || new Date(), - updatedAt: new Date() - }; - - const validation = validateDeck(deckToSave); - if (!validation.isValid) { - alert(`Deck validation failed: ${validation.errors.join(', ')}`); - return; - } - - try { - const deckData = { - id: deckToSave.id, - name: deckToSave.name, - format: deckToSave.format, - user_id: deckToSave.userId, - created_at: deckToSave.createdAt, - updated_at: deckToSave.updatedAt - }; - - // Save or update the deck - const { error: deckError } = await supabase - .from('decks') - .upsert([deckData]) - .select(); - - if (deckError) throw deckError; - - // Delete existing cards if updating - if (initialDeck) { - await supabase - .from('deck_cards') - .delete() - .eq('deck_id', initialDeck.id); - } - - // Save the deck cards - const deckCards = selectedCards.map(card => ({ - deck_id: deckToSave.id, - card_id: card.card.id, - quantity: card.quantity, - is_commander: card.card.type_line?.toLowerCase().includes('legendary creature') || false - })); - - const { error: cardsError } = await supabase - .from('deck_cards') - .insert(deckCards); - - if (cardsError) throw cardsError; - - if (onSave) onSave(); - } catch (error) { - console.error('Error saving deck:', error); - alert('Failed to save deck'); - } - }; - - const currentDeck: Deck = { - id: initialDeck?.id || '', - name: deckName, - format: deckFormat, - cards: selectedCards, - userId: user?.id || '', - createdAt: initialDeck?.createdAt || new Date(), - updatedAt: new Date() - }; - - const validation = validateDeck(currentDeck); - - return ( -
-
-
- {/* Card Search Section */} -
-
-
- - setSearchQuery(e.target.value)} - className="w-full pl-10 pr-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Search for cards..." - /> -
- -
- -
- {searchResults.map(card => ( -
- {card.image_uris?.normal && ( - {card.name} - )} -
-

{card.name}

- -
-
- ))} -
-
- - {/* Deck Builder Section */} -
-
- setDeckName(e.target.value)} - className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Deck Name" - /> - - - - {!validation.isValid && ( -
-
    - {validation.errors.map((error, index) => ( -
  • {error}
  • - ))} -
-
- )} - -
-

- Cards ({selectedCards.reduce((acc, curr) => acc + curr.quantity, 0)}) -

- {selectedCards.map(({ card, quantity }) => ( -
- {card.name} -
-

{card.name}

-
- updateCardQuantity(card.id, parseInt(e.target.value))} - min="1" - max="4" - className="w-16 px-2 py-1 bg-gray-600 border border-gray-500 rounded text-center" - /> - -
- ))} -
- - -
-
-
-
-
- ); +import React, { useState, useEffect } from 'react'; +import { Plus, Search, Save, Trash2 } from 'lucide-react'; +import { Card, Deck } from '../types'; +import { searchCards } from '../services/api'; +import { useAuth } from '../contexts/AuthContext'; +import { supabase } from '../lib/supabase'; +import { validateDeck } from '../utils/deckValidation'; +import MagicCard from './MagicCard'; + +interface DeckManagerProps { + initialDeck?: Deck; + onSave?: () => void; +} + +const calculateManaCurve = (cards: { card: Card; quantity: number }[]) => { + const manaValues = cards.map(({ card }) => { + if (!card.mana_cost) return 0; + // Basic heuristic: count mana symbols + return (card.mana_cost.match(/\{WUBRG0-9]\}/g) || []).length; + }); + + const averageManaValue = manaValues.reduce((a, b) => a + b, 0) / manaValues.length; + return averageManaValue; +}; + +const suggestLandCountAndDistribution = (cards: { card: Card; quantity: number }[], format: string) => { + const formatRules = { + standard: { minCards: 60, targetLands: 24.5 }, + modern: { minCards: 60, targetLands: 24.5 }, + commander: { minCards: 100, targetLands: 36.5 }, + legacy: { minCards: 60, targetLands: 24.5 }, + vintage: { minCards: 60, targetLands: 24.5 }, + pauper: { minCards: 60, targetLands: 24.5 }, + }; + + const { minCards, targetLands } = formatRules[format as keyof typeof formatRules] || formatRules.standard; + const deckSize = cards.reduce((acc, { quantity }) => acc + quantity, 0); + const nonLandCards = cards.reduce((acc, { card, quantity }) => card.type_line?.toLowerCase().includes('land') ? acc : acc + quantity, 0); + const landsToAdd = Math.max(0, minCards - deckSize); + + const colorCounts = { W: 0, U: 0, B: 0, R: 0, G: 0 }; + let totalColorSymbols = 0; + + cards.forEach(({ card, quantity }) => { + if (card.mana_cost) { + const wMatches = (card.mana_cost.match(/\{W\}/g) || []).length; + const uMatches = (card.mana_cost.match(/\{U\}/g) || []).length; + const bMatches = (card.mana_cost.match(/\{B\}/g) || []).length; + const rMatches = (card.mana_cost.match(/\{R\}/g) || []).length; + const gMatches = (card.mana_cost.match(/\{G\}/g) || []).length; + + colorCounts.W += wMatches * quantity; + colorCounts.U += uMatches * quantity; + colorCounts.B += bMatches * quantity; + colorCounts.R += rMatches * quantity; + colorCounts.G += gMatches * quantity; + + totalColorSymbols += (wMatches + uMatches + bMatches + rMatches + gMatches) * quantity; + } + }); + + const landDistribution: { [key: string]: number } = {}; + for (const color in colorCounts) { + const proportion = totalColorSymbols > 0 ? colorCounts[color as keyof typeof colorCounts] / totalColorSymbols : 0; + landDistribution[color] = Math.round(landsToAdd * proportion); + } + + let totalDistributed = Object.values(landDistribution).reduce((acc, count) => acc + count, 0); + + if (totalDistributed > landsToAdd) { + // Find the color with the most lands + let maxColor = ''; + let maxCount = 0; + for (const color in landDistribution) { + if (landDistribution[color] > maxCount) { + maxColor = color; + maxCount = landDistribution[color]; + } + } + + // Reduce the land count of that color + landDistribution[maxColor] = maxCount - 1; + } + + return { landCount: landsToAdd, landDistribution }; +}; + +export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) { + const [searchQuery, setSearchQuery] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [selectedCards, setSelectedCards] = useState<{ card: Card; quantity: number }[]>( + initialDeck?.cards || [] + ); + const [deckName, setDeckName] = useState(initialDeck?.name || ''); + const [deckFormat, setDeckFormat] = useState(initialDeck?.format || 'standard'); + const { user } = useAuth(); + + const handleSearch = async (e: React.FormEvent) => { + e.preventDefault(); + if (!searchQuery.trim()) return; + + try { + const cards = await searchCards(searchQuery); + setSearchResults(cards); + } catch (error) { + console.error('Failed to search cards:', error); + } + }; + + const addCardToDeck = (card: Card) => { + setSelectedCards(prev => { + const isBasicLand = card.name === 'Plains' || card.name === 'Island' || card.name === 'Swamp' || card.name === 'Mountain' || card.name === 'Forest'; + const existing = prev.find(c => c.card.id === card.id); + if (existing) { + return prev.map(c => + c.card.id === card.id + ? { ...c, quantity: isBasicLand ? c.quantity + 1 : Math.min(c.quantity + 1, 4) } + : c + ); + } + return [...prev, { card, quantity: 1 }]; + }); + }; + + const removeCardFromDeck = (cardId: string) => + setSelectedCards(prev => prev.filter(c => c.card.id !== cardId)); + + const updateCardQuantity = (cardId: string, quantity: number) => { + setSelectedCards(prev => { + return prev.map(c => { + if (c.card.id === cardId) { + const isBasicLand = c.card.name === 'Plains' || c.card.name === 'Island' || c.card.name === 'Swamp' || c.card.name === 'Mountain' || c.card.name === 'Forest'; + return { ...c, quantity: quantity }; + } + return c; + }); + }); + }; + + const saveDeck = async () => { + if (!deckName.trim() || selectedCards.length === 0 || !user) return; + + const deckToSave: Deck = { + id: initialDeck?.id || crypto.randomUUID(), + name: deckName, + format: deckFormat, + cards: selectedCards, + userId: user.id, + createdAt: initialDeck?.createdAt || new Date(), + updatedAt: new Date() + }; + + const validation = validateDeck(deckToSave); + if (!validation.isValid) { + alert(`Deck validation failed: ${validation.errors.join(', ')}`); + return; + } + + try { + const deckData = { + id: deckToSave.id, + name: deckToSave.name, + format: deckToSave.format, + user_id: deckToSave.userId, + created_at: deckToSave.createdAt, + updated_at: deckToSave.updatedAt + }; + + // Save or update the deck + const { error: deckError } = await supabase + .from('decks') + .upsert([deckData]) + .select(); + + if (deckError) throw deckError; + + // Delete existing cards if updating + if (initialDeck) { + await supabase + .from('deck_cards') + .delete() + .eq('deck_id', initialDeck.id); + } + + // Save the deck cards + const deckCards = selectedCards.map(card => ({ + deck_id: deckToSave.id, + card_id: card.card.id, + quantity: card.quantity, + is_commander: card.card.type_line?.toLowerCase().includes('legendary creature') || false + })); + + const { error: cardsError } = await supabase + .from('deck_cards') + .insert(deckCards); + + if (cardsError) throw cardsError; + + if (onSave) onSave(); + } catch (error) { + console.error('Error saving deck:', error); + alert('Failed to save deck'); + } + }; + + const currentDeck: Deck = { + id: initialDeck?.id || '', + name: deckName, + format: deckFormat, + cards: selectedCards, + userId: user?.id || '', + createdAt: initialDeck?.createdAt || new Date(), + updatedAt: new Date() + }; + + const validation = validateDeck(currentDeck); + + const deckSize = selectedCards.reduce((acc, curr) => acc + curr.quantity, 0); + const { landCount: suggestedLandCountValue, landDistribution: suggestedLands } = suggestLandCountAndDistribution(selectedCards, deckFormat); + + const totalPrice = selectedCards.reduce((acc, { card, quantity }) => { + const isBasicLand = card.name === 'Plains' || card.name === 'Island' || card.name === 'Swamp' || card.name === 'Mountain' || card.name === 'Forest'; + const price = isBasicLand ? 0 : (card.prices?.usd ? parseFloat(card.prices.usd) : 0); + return acc + price * quantity; + }, 0); + + const addSuggestedLandsToDeck = async () => { + const basicLandCards = { + W: { name: 'Plains', set: 'unh' }, + U: { name: 'Island', set: 'unh' }, + B: { name: 'Swamp', set: 'unh' }, + R: { name: 'Mountain', set: 'unh' }, + G: { name: 'Forest', set: 'unh' }, + }; + + for (const color in suggestedLands) { + const landCount = suggestedLands[color]; + if (landCount > 0) { + const landName = basicLandCards[color]?.name; + const landSet = basicLandCards[color]?.set; + + if (landName && landSet) { + try { + const cards = await searchCards(`${landName} set:${landSet}`); + if (cards && cards.length > 0) { + const landCard = cards[0]; // Take the first matching card + for (let i = 0; i < landCount; i++) { + addCardToDeck(landCard); + } + } + } catch (error) { + console.error(`Failed to add ${landName}:`, error); + } + } + } + } + }; + + return ( +
+
+
+ {/* Card Search Section */} +
+
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Search for cards..." + /> +
+ +
+ +
+ {searchResults.map(card => ( +
+ +
+

{card.name}

+ +
+
+ ))} +
+
+ + {/* Deck Builder Section */} +
+
+ setDeckName(e.target.value)} + className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" + placeholder="Deck Name" + /> + + + + {!validation.isValid && ( +
+
    + {validation.errors.map((error, index) => ( +
  • {error}
  • + ))} +
+
+ )} + +
+

+ Cards ({selectedCards.reduce((acc, curr) => acc + curr.quantity, 0)}) +

+ {selectedCards.map(({ card, quantity }) => ( +
+ {card.name} +
+

{card.name}

+ {card.prices?.usd && ( +
+ ${card.prices.usd} +
+ )} +
+ updateCardQuantity(card.id, parseInt(e.target.value))} + min="1" + className="w-16 px-2 py-1 bg-gray-600 border border-gray-500 rounded text-center" + /> + +
+ ))} +
+ +
+ Total Price: ${totalPrice.toFixed(2)} +
+ + {deckSize > 0 && ( +
+ Suggested Land Count: {suggestedLandCountValue} + {Object.entries(suggestedLands).map(([landType, count]) => ( +
+ {landType}: {count} +
+ ))} +
+ )} + + {deckSize > 0 && ( + + )} + + +
+
+
+
+
+ ); } diff --git a/src/components/MagicCard.tsx b/src/components/MagicCard.tsx new file mode 100644 index 0000000..ea7bd1c --- /dev/null +++ b/src/components/MagicCard.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Card } from '../types'; + +interface MagicCardProps { + card: Card; +} + +const MagicCard = ({ card }: MagicCardProps) => { + return ( +
+ {card.image_uris?.normal ? ( + {card.name} + ) : ( +
+ No Image Available +
+ )} + {card.prices?.usd && ( +
+ ${card.prices.usd} +
+ )} +
+ ); +}; + +export default MagicCard; diff --git a/src/utils/deckValidation.ts b/src/utils/deckValidation.ts index 53be628..d468882 100644 --- a/src/utils/deckValidation.ts +++ b/src/utils/deckValidation.ts @@ -67,7 +67,10 @@ export function validateDeck(deck: Deck): DeckValidation { } cardCounts.forEach((count, cardName) => { - if (count > rules.maxCopies) { + const card = deck.cards.find(c => c.card.id === cardName)?.card; + const isBasicLand = card?.name === 'Plains' || card?.name === 'Island' || card?.name === 'Swamp' || card?.name === 'Mountain' || card?.name === 'Forest'; + + if (!isBasicLand && count > rules.maxCopies) { errors.push(`${cardName} has too many copies (max ${rules.maxCopies})`); } });