mirror of
https://github.com/xavidop/cardex.git
synced 2026-08-28 17:01:07 +00:00
feat: add grading feature
This commit is contained in:
@@ -8,6 +8,11 @@ const nextConfig: NextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
experimental: {
|
||||
serverActions: {
|
||||
bodySizeLimit: '10mb', // Increased for card image uploads
|
||||
},
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
Generated
+149
-100
@@ -563,7 +563,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz",
|
||||
"integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@firebase/ai": {
|
||||
"version": "1.4.1",
|
||||
@@ -628,7 +629,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz",
|
||||
"integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.6.18",
|
||||
"@firebase/logger": "0.4.4",
|
||||
@@ -695,7 +695,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz",
|
||||
"integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@firebase/app": "0.13.2",
|
||||
"@firebase/component": "0.6.18",
|
||||
@@ -711,8 +710,7 @@
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
|
||||
"integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/auth-compat": {
|
||||
"version": "0.5.28",
|
||||
@@ -1176,7 +1174,6 @@
|
||||
"integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -1454,7 +1451,6 @@
|
||||
"integrity": "sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "^1.3.0",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
@@ -1654,6 +1650,7 @@
|
||||
"integrity": "sha512-gOnCAbFgAYKRozywLsxagdevTF7Gm+2Ncz5u5CQAuOv/2VCa0rdGJWvJFDOftPx1tc+q8TXiC2pEJfFKu+yeMQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@google-cloud/paginator": "^5.0.0",
|
||||
"@google-cloud/projectify": "^4.0.0",
|
||||
@@ -1681,6 +1678,7 @@
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
@@ -1694,6 +1692,7 @@
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
@@ -2432,7 +2431,6 @@
|
||||
"integrity": "sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
@@ -2728,22 +2726,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
|
||||
@@ -2830,7 +2812,6 @@
|
||||
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
@@ -3015,7 +2996,6 @@
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -3111,7 +3091,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz",
|
||||
"integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/semantic-conventions": "1.25.1"
|
||||
},
|
||||
@@ -4301,7 +4280,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz",
|
||||
"integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "1.25.1"
|
||||
@@ -4335,7 +4313,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz",
|
||||
"integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "1.25.1",
|
||||
"@opentelemetry/resources": "1.25.1",
|
||||
@@ -4380,7 +4357,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz",
|
||||
"integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "1.25.1",
|
||||
"@opentelemetry/resources": "1.25.1",
|
||||
@@ -5602,7 +5578,6 @@
|
||||
"version": "5.67.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.67.1.tgz",
|
||||
"integrity": "sha512-fH5u4JLwB6A+wLFdi8wWBWAYoJV5deYif2OveJ26ktAWjU499uvVFS1wPWnyEyq5LvZX1MZInvv9QRaIZANRaQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.67.1"
|
||||
},
|
||||
@@ -5647,6 +5622,7 @@
|
||||
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
@@ -5685,6 +5661,7 @@
|
||||
"integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^4.17.33",
|
||||
@@ -5698,6 +5675,7 @@
|
||||
"integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
@@ -5710,7 +5688,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
@@ -5724,6 +5703,7 @@
|
||||
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/ms": "*",
|
||||
"@types/node": "*"
|
||||
@@ -5751,14 +5731,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/mysql": {
|
||||
"version": "2.15.22",
|
||||
@@ -5811,21 +5793,22 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -5836,7 +5819,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
|
||||
"integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
@@ -5878,6 +5860,7 @@
|
||||
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -5888,6 +5871,7 @@
|
||||
"integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/http-errors": "*",
|
||||
"@types/node": "*",
|
||||
@@ -5900,6 +5884,7 @@
|
||||
"integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/mime": "^1",
|
||||
"@types/node": "*"
|
||||
@@ -5981,7 +5966,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -6173,6 +6157,7 @@
|
||||
"integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"retry": "0.13.1"
|
||||
}
|
||||
@@ -6252,29 +6237,58 @@
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"version": "1.20.4",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
||||
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"bytes": "~3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"destroy": "~1.2.0",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"on-finished": "~2.4.1",
|
||||
"qs": "~6.14.0",
|
||||
"raw-body": "~2.5.3",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
@@ -7407,21 +7421,6 @@
|
||||
"express": ">= 4.11"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
@@ -7476,6 +7475,7 @@
|
||||
"integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
@@ -7540,6 +7540,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"strnum": "^1.1.1"
|
||||
},
|
||||
@@ -7663,7 +7664,6 @@
|
||||
"resolved": "https://registry.npmjs.org/firebase/-/firebase-11.10.0.tgz",
|
||||
"integrity": "sha512-nKBXoDzF0DrXTBQJlZa+sbC5By99ysYU1D6PkMRYknm0nCW7rJly47q492Ht7Ndz5MeYSBuboKuhS1e6mFC03w==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@firebase/ai": "1.4.1",
|
||||
"@firebase/analytics": "0.10.17",
|
||||
@@ -7701,6 +7701,7 @@
|
||||
"integrity": "sha512-GdPA/t0+Cq8p1JnjFRBmxRxAGvF/kl2yfdhALl38PrRp325YxyQ5aNaHui0XmaKcKiGRFIJ/EgBNWFoDP0onjw==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^3.0.0",
|
||||
"@firebase/database-compat": "^2.0.0",
|
||||
@@ -7728,6 +7729,7 @@
|
||||
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -7737,7 +7739,8 @@
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/firebase-admin/node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
@@ -7749,6 +7752,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
@@ -7979,7 +7983,6 @@
|
||||
"resolved": "https://registry.npmjs.org/genkit/-/genkit-1.24.0.tgz",
|
||||
"integrity": "sha512-9BjPrLULfWdzauibKkbZcCf2LVu0mW2EW6EXHK/cTber6QdiP/guA5mXahaaDWBo+qdIKpInXAYDEzcRUmCVSA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@genkit-ai/ai": "1.24.0",
|
||||
"@genkit-ai/core": "1.24.0",
|
||||
@@ -8905,6 +8908,7 @@
|
||||
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
@@ -9017,6 +9021,7 @@
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
@@ -9040,6 +9045,7 @@
|
||||
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
@@ -9052,6 +9058,7 @@
|
||||
"integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.2",
|
||||
"safe-buffer": "^5.0.1"
|
||||
@@ -9062,7 +9069,8 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.1",
|
||||
@@ -9081,6 +9089,7 @@
|
||||
"integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/jsonwebtoken": "^9.0.4",
|
||||
@@ -9099,6 +9108,7 @@
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
@@ -9116,7 +9126,8 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.1",
|
||||
@@ -9158,7 +9169,8 @@
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
|
||||
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
@@ -9199,49 +9211,56 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.mapvalues": {
|
||||
"version": "4.6.0",
|
||||
@@ -9261,7 +9280,8 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
@@ -9332,6 +9352,7 @@
|
||||
"integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lru-cache": "6.0.0"
|
||||
@@ -9343,6 +9364,7 @@
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
@@ -9584,7 +9606,6 @@
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
|
||||
"integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.9",
|
||||
"@swc/helpers": "0.5.15",
|
||||
@@ -9700,6 +9721,7 @@
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6.13.0"
|
||||
}
|
||||
@@ -9906,6 +9928,7 @@
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
@@ -10147,7 +10170,6 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.8",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -10412,12 +10434,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@@ -10455,25 +10477,53 @@
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
"bytes": "~3.1.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -10485,7 +10535,6 @@
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -10498,7 +10547,6 @@
|
||||
"version": "7.54.2",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
|
||||
"integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -10720,6 +10768,7 @@
|
||||
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
@@ -11346,7 +11395,8 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/stubs": {
|
||||
"version": "3.0.0",
|
||||
@@ -11433,7 +11483,6 @@
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@@ -11993,7 +12042,6 @@
|
||||
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@colors/colors": "^1.6.0",
|
||||
"@dabh/diagnostics": "^2.0.2",
|
||||
@@ -12201,7 +12249,8 @@
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.0",
|
||||
@@ -12293,6 +12342,7 @@
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -12304,7 +12354,6 @@
|
||||
"version": "3.24.2",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
||||
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
// This file is automatically generated - edits will be lost!
|
||||
'use server';
|
||||
|
||||
/**
|
||||
* @fileOverview Uses Gemini Vision to grade a TCG card's condition based on PSA/BGS/CGC standards.
|
||||
*
|
||||
* - gradeTCGCard - A function that handles the card grading process.
|
||||
* - GradeTCGCardInput - The input type for the gradeTCGCard function.
|
||||
* - GradeTCGCardOutput - The return type for the gradeTCGCard function.
|
||||
*/
|
||||
|
||||
import {ai, createUserAI} from '@/ai/genkit';
|
||||
import {z} from 'genkit';
|
||||
import type { TCGGame } from '@/types';
|
||||
import { tcgGameSchema } from '@/constants';
|
||||
|
||||
const GradeTCGCardInputSchema = z.object({
|
||||
frontPhotoDataUri: z
|
||||
.string()
|
||||
.describe(
|
||||
"A photo of the front of a trading card, as a data URI that must include a MIME type and use Base64 encoding. Expected format: 'data:<mimetype>;base64,<encoded_data>'."
|
||||
),
|
||||
backPhotoDataUri: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Optional photo of the back of the trading card, as a data URI. If provided, will be used for more accurate centering and surface grading."
|
||||
),
|
||||
userId: z.string().describe("The user ID for personalized API key usage.").optional(),
|
||||
cardName: z.string().describe("The name of the card being graded.").optional(),
|
||||
cardSet: z.string().describe("The set the card belongs to.").optional(),
|
||||
game: tcgGameSchema.optional().describe("The TCG game this card is from."),
|
||||
gradingScale: z.enum(['PSA', 'BGS', 'CGC']).optional().describe("The grading scale to use. Default is PSA."),
|
||||
});
|
||||
export type GradeTCGCardInput = z.infer<typeof GradeTCGCardInputSchema>;
|
||||
|
||||
const GradeTCGCardOutputSchema = z.object({
|
||||
gradingResult: z.object({
|
||||
overallGrade: z.number().min(1).max(10).describe("Overall grade on a 1-10 scale."),
|
||||
centering: z.object({
|
||||
score: z.number().min(1).max(10).describe("Centering score 1-10."),
|
||||
frontCentering: z.string().describe("Front centering ratio (e.g., '55/45')."),
|
||||
backCentering: z.string().describe("Back centering ratio (e.g., '60/40')."),
|
||||
notes: z.string().describe("Notes about centering condition."),
|
||||
}).describe("Centering analysis."),
|
||||
corners: z.object({
|
||||
score: z.number().min(1).max(10).describe("Corners score 1-10."),
|
||||
condition: z.string().describe("Corners condition description."),
|
||||
notes: z.string().describe("Notes about corner condition."),
|
||||
}).describe("Corners analysis."),
|
||||
edges: z.object({
|
||||
score: z.number().min(1).max(10).describe("Edges score 1-10."),
|
||||
condition: z.string().describe("Edges condition description."),
|
||||
notes: z.string().describe("Notes about edge condition."),
|
||||
}).describe("Edges analysis."),
|
||||
surface: z.object({
|
||||
score: z.number().min(1).max(10).describe("Surface score 1-10."),
|
||||
condition: z.string().describe("Surface condition description."),
|
||||
notes: z.string().describe("Notes about surface condition."),
|
||||
}).describe("Surface analysis."),
|
||||
gradingScale: z.enum(['PSA', 'BGS', 'CGC']).describe("The grading scale used."),
|
||||
gradeName: z.string().describe("Grade name (e.g., 'GEM MINT', 'MINT', 'NEAR MINT')."),
|
||||
estimatedValue: z.string().optional().describe("Estimated market value if card info provided."),
|
||||
recommendations: z.array(z.string()).describe("Recommendations for card protection/preservation."),
|
||||
detailedAnalysis: z.string().describe("Overall detailed assessment of the card's condition."),
|
||||
}).describe("Comprehensive grading result.").optional(),
|
||||
error: z.string().describe("Error message if the card could not be graded.").optional()
|
||||
});
|
||||
|
||||
export type GradeTCGCardOutput = z.infer<typeof GradeTCGCardOutputSchema>;
|
||||
|
||||
export async function gradeTCGCard(input: GradeTCGCardInput): Promise<GradeTCGCardOutput> {
|
||||
return gradeTCGCardFlow(input);
|
||||
}
|
||||
|
||||
async function createGradePromptAndFlow(userAI: any) {
|
||||
const gradeScale = (scale: string = 'PSA') => {
|
||||
const scales = {
|
||||
PSA: `PSA Grading Scale:
|
||||
- 10 (GEM MINT): Perfect card with no visible flaws
|
||||
- 9 (MINT): Minor imperfections allowed
|
||||
- 8 (NM-MT): Near Mint to Mint condition
|
||||
- 7 (NM): Near Mint condition
|
||||
- 6 (EX-MT): Excellent to Mint
|
||||
- 5 (EX): Excellent condition
|
||||
- 4 (VG-EX): Very Good to Excellent
|
||||
- 3 (VG): Very Good condition
|
||||
- 2 (GOOD): Good condition with visible wear
|
||||
- 1 (POOR): Poor condition with heavy wear`,
|
||||
BGS: `Beckett Grading Scale (BGS):
|
||||
- 10 (PRISTINE): Perfect in every way
|
||||
- 9.5 (GEM MINT): Near perfect card
|
||||
- 9 (MINT): Minor imperfections
|
||||
- 8.5 (NM-MT+): Near Mint to Mint+
|
||||
- 8 (NM-MT): Near Mint to Mint
|
||||
- 7.5 (NM+): Near Mint+
|
||||
- 7 (NM): Near Mint
|
||||
- 6.5 (EX-NM+): Excellent to Near Mint+
|
||||
- 6 (EX-NM): Excellent to Near Mint
|
||||
- 5.5 (EX+): Excellent+
|
||||
- 5 (EX): Excellent
|
||||
- Lower grades for worse condition`,
|
||||
CGC: `CGC Grading Scale:
|
||||
- 10 (PRISTINE): Perfect card
|
||||
- 9.5 (GEM MINT): Near perfect
|
||||
- 9 (MINT): Mint condition
|
||||
- 8.5 (NM/MINT+): Near Mint to Mint+
|
||||
- 8 (NM/MINT): Near Mint to Mint
|
||||
- 7.5 (NM+): Near Mint+
|
||||
- 7 (NM): Near Mint
|
||||
- 6.5 (EX/NM+): Excellent to Near Mint+
|
||||
- 6 (EX/NM): Excellent to Near Mint
|
||||
- 5.5 (EX+): Excellent+
|
||||
- 5 (EX): Excellent condition
|
||||
- Lower grades for worse condition`
|
||||
};
|
||||
return scales[scale as keyof typeof scales] || scales.PSA;
|
||||
};
|
||||
|
||||
const gradeTCGCardPrompt = userAI.definePrompt({
|
||||
name: 'gradeTCGCardPrompt',
|
||||
input: {schema: GradeTCGCardInputSchema},
|
||||
output: {schema: GradeTCGCardOutputSchema},
|
||||
prompt: `You are an expert trading card grading professional with extensive knowledge of PSA, BGS (Beckett), and CGC grading standards. You specialize in evaluating TCG cards including Pokémon, One Piece, Lorcana, Magic: The Gathering, and Dragon Ball cards.
|
||||
|
||||
{{#if gradingScale}}
|
||||
Use the ${gradeScale('{{gradingScale}}')} grading scale.
|
||||
{{else}}
|
||||
Use the ${gradeScale('PSA')} grading scale (default).
|
||||
{{/if}}
|
||||
|
||||
Analyze the following card image(s) and provide a comprehensive grading assessment:
|
||||
|
||||
Front Photo: {{media url=frontPhotoDataUri}}
|
||||
{{#if backPhotoDataUri}}
|
||||
Back Photo: {{media url=backPhotoDataUri}}
|
||||
{{/if}}
|
||||
|
||||
{{#if cardName}}
|
||||
Card Name: {{cardName}}
|
||||
{{/if}}
|
||||
{{#if cardSet}}
|
||||
Card Set: {{cardSet}}
|
||||
{{/if}}
|
||||
{{#if game}}
|
||||
Game: {{game}}
|
||||
{{/if}}
|
||||
|
||||
{{#if backPhotoDataUri}}
|
||||
Note: Both front and back images are provided. Use both images to provide a more accurate assessment of centering, surface quality, and overall condition.
|
||||
{{else}}
|
||||
Note: Only front image provided. Estimate back centering and condition based on typical manufacturing tolerances.
|
||||
{{/if}}
|
||||
|
||||
GRADING CRITERIA TO EVALUATE:
|
||||
|
||||
1. **CENTERING** (Front and Back):
|
||||
- Measure the borders on all four sides
|
||||
- Front centering should be within specific tolerances (e.g., 60/40 for PSA 9, 55/45 for PSA 10)
|
||||
- Back centering has slightly more tolerance
|
||||
- Provide specific ratios (e.g., "55/45" for left-right, top-bottom)
|
||||
|
||||
2. **CORNERS**:
|
||||
- Examine all four corners for wear, whitening, or rounding
|
||||
- Sharp corners = higher grade
|
||||
- Any fraying, bending, or whitening reduces the grade
|
||||
- Note which corners (if any) show issues
|
||||
|
||||
3. **EDGES**:
|
||||
- Check all four edges for wear, chipping, or roughness
|
||||
- Look for whitening along edges
|
||||
- Clean, sharp edges = higher grade
|
||||
- Note any specific edge issues
|
||||
|
||||
4. **SURFACE**:
|
||||
- Look for scratches, print lines, print dots
|
||||
- Check for indentations, creases, or wrinkles
|
||||
- Evaluate holographic surface quality (if applicable)
|
||||
- Look for any staining or discoloration
|
||||
- Note any surface imperfections
|
||||
|
||||
IMPORTANT GRADING RULES:
|
||||
- A card can only be as good as its worst attribute
|
||||
- Centering issues often limit the maximum grade
|
||||
- Surface defects can significantly drop the grade
|
||||
- Be realistic and conservative in grading
|
||||
- Corner and edge wear are critical factors
|
||||
|
||||
Provide your assessment in the following JSON format:
|
||||
{
|
||||
"gradingResult": {
|
||||
"overallGrade": 8.5,
|
||||
"centering": {
|
||||
"score": 9,
|
||||
"frontCentering": "55/45",
|
||||
"backCentering": "60/40",
|
||||
"notes": "Front centering is excellent, back slightly off-center left to right"
|
||||
},
|
||||
"corners": {
|
||||
"score": 8,
|
||||
"condition": "Mostly sharp with minor wear",
|
||||
"notes": "Top right corner shows slight whitening, other corners are sharp"
|
||||
},
|
||||
"edges": {
|
||||
"score": 9,
|
||||
"condition": "Clean with minimal wear",
|
||||
"notes": "Edges are clean and sharp with no visible whitening"
|
||||
},
|
||||
"surface": {
|
||||
"score": 8,
|
||||
"condition": "Minor imperfections visible",
|
||||
"notes": "Small print line visible near top edge, holographic surface shows minor wear"
|
||||
},
|
||||
"gradingScale": "PSA",
|
||||
"gradeName": "NM-MT",
|
||||
"estimatedValue": "$50-75 (based on condition)",
|
||||
"recommendations": [
|
||||
"Store in a penny sleeve and top loader",
|
||||
"Keep away from direct sunlight",
|
||||
"Consider professional grading if valuable",
|
||||
"Handle with clean hands or gloves"
|
||||
],
|
||||
"detailedAnalysis": "This card presents in Near Mint to Mint condition with excellent centering and clean edges. The primary factors preventing a higher grade are the slight corner wear on the top right corner and a minor print line on the surface. Overall, this is a well-preserved card that would likely grade PSA 8-8.5. With proper storage, it should maintain its condition."
|
||||
}
|
||||
}
|
||||
|
||||
If the image is unclear, not a trading card, or cannot be properly graded, respond with:
|
||||
{
|
||||
"error": "Unable to grade card. [Specific reason: image too blurry, not a trading card, etc.]"
|
||||
}`,
|
||||
});
|
||||
|
||||
const gradeTCGCardFlow = userAI.defineFlow(
|
||||
{
|
||||
name: 'gradeTCGCardFlow',
|
||||
inputSchema: GradeTCGCardInputSchema,
|
||||
outputSchema: GradeTCGCardOutputSchema,
|
||||
},
|
||||
async (input: GradeTCGCardInput) => {
|
||||
try {
|
||||
const {output} = await gradeTCGCardPrompt(input);
|
||||
return output!;
|
||||
} catch (error: any) {
|
||||
console.error('Error during card grading:', error);
|
||||
return {error: 'Failed to grade trading card.'};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return gradeTCGCardFlow;
|
||||
}
|
||||
|
||||
const gradeTCGCardFlow = ai.defineFlow(
|
||||
{
|
||||
name: 'gradeTCGCardFlow',
|
||||
inputSchema: GradeTCGCardInputSchema,
|
||||
outputSchema: GradeTCGCardOutputSchema,
|
||||
},
|
||||
async (input: GradeTCGCardInput) => {
|
||||
try {
|
||||
const userAI = await createUserAI(input.userId || '');
|
||||
const flow = await createGradePromptAndFlow(userAI);
|
||||
return await flow(input);
|
||||
} catch (error: any) {
|
||||
console.error('Error during card grading:', error);
|
||||
return {error: 'Failed to grade trading card.'};
|
||||
}
|
||||
}
|
||||
);
|
||||
+2
-2
@@ -9,7 +9,7 @@ export const ai = genkit({
|
||||
plugins: [
|
||||
googleAI({apiKey: defaultApiKey})
|
||||
],
|
||||
model: 'googleai/gemini-2.5-pro-preview-05-06',
|
||||
model: 'googleai/gemini-3-pro-preview',
|
||||
});
|
||||
|
||||
// Function to create genkit instance with user API key
|
||||
@@ -23,7 +23,7 @@ export async function createUserAI(userId: string) {
|
||||
plugins: [
|
||||
googleAI({apiKey: userApiKey})
|
||||
],
|
||||
model: 'googleai/gemini-2.5-pro-preview-05-06',
|
||||
model: 'googleai/gemini-3-pro-preview',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NextRequest } from 'next/server';
|
||||
import { gradeTCGCard } from '@/ai/flows/grade-tcg-card';
|
||||
import { handleApiRequest } from '@/lib/api-handler';
|
||||
|
||||
/**
|
||||
* Required fields for card grading
|
||||
*/
|
||||
const REQUIRED_FIELDS = [
|
||||
'frontPhotoDataUri'
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* POST /api/grade-card
|
||||
* Grades a TCG card's condition using AI based on professional grading standards
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
return handleApiRequest(request, REQUIRED_FIELDS, gradeTCGCard);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import CardGrader from '@/components/cards/CardGrader';
|
||||
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
|
||||
|
||||
export default function GradeCardPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="text-center space-y-2">
|
||||
<h1 className="text-3xl font-bold tracking-tight">Pre-Grade Your Card</h1>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
Get a professional grading assessment before sending to PSA, BGS, or CGC
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Upload a clear photo of your card to receive detailed condition analysis and grading score
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Keys Warning - only show Gemini key warning for grading */}
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<ApiKeysWarning
|
||||
requiredKeys={['geminiApiKey']}
|
||||
showDismiss={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardGrader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ScanLine, Sparkles, BookOpen, Camera } from 'lucide-react';
|
||||
import { ScanLine, Sparkles, BookOpen, Camera, Award } from 'lucide-react';
|
||||
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
|
||||
|
||||
export default function DashboardPage() {
|
||||
@@ -24,7 +24,7 @@ export default function DashboardPage() {
|
||||
<ApiKeysWarning />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 max-w-6xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 max-w-7xl mx-auto">
|
||||
<Card className="hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
@@ -44,6 +44,25 @@ export default function DashboardPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Award className="mr-2 h-5 w-5" />
|
||||
Grade Cards
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Pre-grade cards before sending to PSA, BGS, or CGC
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button asChild className="w-full">
|
||||
<Link href="/dashboard/grade">
|
||||
Grade Card
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
'use client';
|
||||
|
||||
import { useState, type ChangeEvent, useRef } from 'react';
|
||||
import { gradeTCGCard, type GradeTCGCardOutput } from '@/ai/flows/grade-tcg-card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Loader2, Upload, Camera, Award, TrendingUp, Shield } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useApiKeys } from '@/hooks/useApiKeys';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../ui/card';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import type { TCGGame } from '@/types';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
|
||||
export default function CardGrader() {
|
||||
const { hasGeminiKey } = useApiKeys();
|
||||
const [isGrading, setIsGrading] = useState(false);
|
||||
const [gradingResult, setGradingResult] = useState<GradeTCGCardOutput | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Front image (required)
|
||||
const [frontImagePreview, setFrontImagePreview] = useState<string | null>(null);
|
||||
const [frontImageDataUrl, setFrontImageDataUrl] = useState<string | null>(null);
|
||||
|
||||
// Back image (optional)
|
||||
const [backImagePreview, setBackImagePreview] = useState<string | null>(null);
|
||||
const [backImageDataUrl, setBackImageDataUrl] = useState<string | null>(null);
|
||||
|
||||
// Optional metadata for better grading
|
||||
const [cardName, setCardName] = useState('');
|
||||
const [cardSet, setCardSet] = useState('');
|
||||
const [game, setGame] = useState<TCGGame | ''>('');
|
||||
const [gradingScale, setGradingScale] = useState<'PSA' | 'BGS' | 'CGC'>('PSA');
|
||||
|
||||
const { toast } = useToast();
|
||||
const { user } = useAuth();
|
||||
const frontFileInputRef = useRef<HTMLInputElement>(null);
|
||||
const backFileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFrontImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
// Reset previous grading state
|
||||
setGradingResult(null);
|
||||
setError(null);
|
||||
setFrontImageDataUrl(null);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const dataUrl = reader.result as string;
|
||||
setFrontImagePreview(dataUrl);
|
||||
setFrontImageDataUrl(dataUrl);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
// Reset previous grading state
|
||||
setGradingResult(null);
|
||||
setError(null);
|
||||
setBackImageDataUrl(null);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const dataUrl = reader.result as string;
|
||||
setBackImagePreview(dataUrl);
|
||||
setBackImageDataUrl(dataUrl);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGrade = async () => {
|
||||
if (!frontImageDataUrl) {
|
||||
toast({
|
||||
title: 'No front image selected',
|
||||
description: 'Please select the front image of the card to grade.',
|
||||
variant: 'destructive'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasGeminiKey) {
|
||||
setError('Gemini API key is required to grade trading cards. Please configure it in Settings.');
|
||||
toast({
|
||||
title: 'API Key Required',
|
||||
description: 'Gemini API key is required. Please configure it in Settings.',
|
||||
variant: 'destructive'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGrading(true);
|
||||
setError(null);
|
||||
setGradingResult(null);
|
||||
|
||||
try {
|
||||
const result = await gradeTCGCard({
|
||||
frontPhotoDataUri: frontImageDataUrl,
|
||||
backPhotoDataUri: backImageDataUrl || undefined,
|
||||
userId: user?.uid || '',
|
||||
cardName: cardName || undefined,
|
||||
cardSet: cardSet || undefined,
|
||||
game: game || undefined,
|
||||
gradingScale
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
toast({
|
||||
title: 'Grading Failed',
|
||||
description: result.error,
|
||||
variant: 'destructive'
|
||||
});
|
||||
} else if (result.gradingResult) {
|
||||
setGradingResult(result);
|
||||
toast({
|
||||
title: 'Grading Complete',
|
||||
description: `Card graded: ${result.gradingResult.gradeName}`,
|
||||
variant: 'default'
|
||||
});
|
||||
} else {
|
||||
setError('Unexpected response from grading service.');
|
||||
toast({
|
||||
title: 'Grading Error',
|
||||
description: 'Unexpected response from grading service.',
|
||||
variant: 'destructive'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Grading error:', e);
|
||||
const errorMessage = (e instanceof Error) ? e.message : 'An unknown error occurred during grading.';
|
||||
setError(errorMessage);
|
||||
toast({
|
||||
title: 'Grading Error',
|
||||
description: errorMessage,
|
||||
variant: 'destructive'
|
||||
});
|
||||
} finally {
|
||||
setIsGrading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getGradeColor = (grade: number) => {
|
||||
if (grade >= 9.5) return 'text-green-600 dark:text-green-400';
|
||||
if (grade >= 8) return 'text-blue-600 dark:text-blue-400';
|
||||
if (grade >= 6) return 'text-yellow-600 dark:text-yellow-400';
|
||||
return 'text-orange-600 dark:text-orange-400';
|
||||
};
|
||||
|
||||
const getScorePercentage = (score: number) => (score / 10) * 100;
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-7xl mx-auto space-y-6">
|
||||
{/* Show results in optimized layout when grading is complete */}
|
||||
{gradingResult?.gradingResult ? (
|
||||
<div className="space-y-6">
|
||||
{/* Top Section - Overall Grade & Images */}
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-col md:flex-row gap-6 items-start">
|
||||
{/* Images - Compact on the left */}
|
||||
<div className="flex gap-4 md:w-1/3">
|
||||
{frontImagePreview && (
|
||||
<div className="flex-1">
|
||||
<Label className="text-xs font-medium mb-2 block">Front</Label>
|
||||
<div className="relative w-full aspect-[2.5/3.5] border rounded-lg overflow-hidden">
|
||||
<Image
|
||||
src={frontImagePreview}
|
||||
alt="Card front"
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{backImagePreview && (
|
||||
<div className="flex-1">
|
||||
<Label className="text-xs font-medium mb-2 block">Back</Label>
|
||||
<div className="relative w-full aspect-[2.5/3.5] border rounded-lg overflow-hidden">
|
||||
<Image
|
||||
src={backImagePreview}
|
||||
alt="Card back"
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Overall Grade - Center */}
|
||||
<div className="flex-1 text-center space-y-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Badge variant="outline" className="text-base px-3 py-1">
|
||||
{gradingResult.gradingResult.gradingScale}
|
||||
</Badge>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setGradingResult(null);
|
||||
setError(null);
|
||||
}}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Grade Another
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground mb-2">Overall Grade</p>
|
||||
<div className={`text-7xl font-bold ${getGradeColor(gradingResult.gradingResult.overallGrade)}`}>
|
||||
{gradingResult.gradingResult.overallGrade}
|
||||
</div>
|
||||
<p className="text-2xl font-semibold mt-2">{gradingResult.gradingResult.gradeName}</p>
|
||||
</div>
|
||||
{gradingResult.gradingResult.estimatedValue && (
|
||||
<div className="flex items-center justify-center gap-2 pt-2">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
<p className="text-base font-medium">
|
||||
Est. Value: {gradingResult.gradingResult.estimatedValue}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* AI Disclaimer */}
|
||||
<div className="mt-4 p-3 bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
||||
<p className="text-xs text-amber-800 dark:text-amber-200 leading-relaxed">
|
||||
⚠️ This grade is generated by Google Gemini AI and is for reference only.
|
||||
Final professional grading results may vary. Always consult with PSA, BGS, or CGC for official grading.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Detailed Scores Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Centering */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center justify-between">
|
||||
<span>Centering</span>
|
||||
<span className="text-lg font-bold">{gradingResult.gradingResult.centering.score}/10</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Progress value={getScorePercentage(gradingResult.gradingResult.centering.score)} className="h-2" />
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p>Front: {gradingResult.gradingResult.centering.frontCentering}</p>
|
||||
<p>Back: {gradingResult.gradingResult.centering.backCentering}</p>
|
||||
<p className="pt-1">{gradingResult.gradingResult.centering.notes}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Corners */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center justify-between">
|
||||
<span>Corners</span>
|
||||
<span className="text-lg font-bold">{gradingResult.gradingResult.corners.score}/10</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Progress value={getScorePercentage(gradingResult.gradingResult.corners.score)} className="h-2" />
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p className="font-medium">{gradingResult.gradingResult.corners.condition}</p>
|
||||
<p>{gradingResult.gradingResult.corners.notes}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Edges */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center justify-between">
|
||||
<span>Edges</span>
|
||||
<span className="text-lg font-bold">{gradingResult.gradingResult.edges.score}/10</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Progress value={getScorePercentage(gradingResult.gradingResult.edges.score)} className="h-2" />
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p className="font-medium">{gradingResult.gradingResult.edges.condition}</p>
|
||||
<p>{gradingResult.gradingResult.edges.notes}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Surface */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center justify-between">
|
||||
<span>Surface</span>
|
||||
<span className="text-lg font-bold">{gradingResult.gradingResult.surface.score}/10</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Progress value={getScorePercentage(gradingResult.gradingResult.surface.score)} className="h-2" />
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p className="font-medium">{gradingResult.gradingResult.surface.condition}</p>
|
||||
<p>{gradingResult.gradingResult.surface.notes}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Analysis & Recommendations */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Analysis</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{gradingResult.gradingResult.detailedAnalysis}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Recommendations</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2">
|
||||
{gradingResult.gradingResult.recommendations.map((rec, index) => (
|
||||
<li key={index} className="flex items-start gap-2 text-sm">
|
||||
<span className="text-primary mt-0.5">•</span>
|
||||
<span className="text-muted-foreground">{rec}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Upload Form - Show when no results */
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Award className="h-6 w-6" />
|
||||
Card Grading Tool
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Upload a photo of your card to receive a professional-grade assessment
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Image Upload Section */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium">Card Images</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Front Image */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Front (Required)</Label>
|
||||
<div className="flex flex-col items-center justify-center border-2 border-dashed rounded-lg p-4 hover:border-primary/50 transition-colors">
|
||||
{frontImagePreview ? (
|
||||
<div className="relative w-full aspect-[2.5/3.5]">
|
||||
<Image
|
||||
src={frontImagePreview}
|
||||
alt="Card front"
|
||||
fill
|
||||
className="object-contain rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center space-y-2 py-8">
|
||||
<Camera className="h-12 w-12 mx-auto text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Front of Card</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Show all edges & corners
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => frontFileInputRef.current?.click()}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
size="sm"
|
||||
>
|
||||
<Upload className="h-4 w-4 mr-2" />
|
||||
{frontImagePreview ? 'Change Front' : 'Upload Front'}
|
||||
</Button>
|
||||
<input
|
||||
ref={frontFileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleFrontImageChange}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Back Image */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Back (Optional)</Label>
|
||||
<div className="flex flex-col items-center justify-center border-2 border-dashed rounded-lg p-4 hover:border-primary/50 transition-colors">
|
||||
{backImagePreview ? (
|
||||
<div className="relative w-full aspect-[2.5/3.5]">
|
||||
<Image
|
||||
src={backImagePreview}
|
||||
alt="Card back"
|
||||
fill
|
||||
className="object-contain rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center space-y-2 py-8">
|
||||
<Camera className="h-12 w-12 mx-auto text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Back of Card</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
For accurate centering
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => backFileInputRef.current?.click()}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
size="sm"
|
||||
>
|
||||
<Upload className="h-4 w-4 mr-2" />
|
||||
{backImagePreview ? 'Change Back' : 'Upload Back'}
|
||||
</Button>
|
||||
<input
|
||||
ref={backFileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleBackImageChange}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Optional Card Metadata */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium">Optional Card Information</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Providing card details helps with value estimation
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cardName">Card Name</Label>
|
||||
<Input
|
||||
id="cardName"
|
||||
placeholder="e.g., Charizard"
|
||||
value={cardName}
|
||||
onChange={(e) => setCardName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cardSet">Card Set</Label>
|
||||
<Input
|
||||
id="cardSet"
|
||||
placeholder="e.g., Base Set"
|
||||
value={cardSet}
|
||||
onChange={(e) => setCardSet(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="game">Game (Optional)</Label>
|
||||
<Select value={game || undefined} onValueChange={(value) => setGame(value as TCGGame)}>
|
||||
<SelectTrigger id="game">
|
||||
<SelectValue placeholder="Auto-detect from image" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="pokemon">Pokémon</SelectItem>
|
||||
<SelectItem value="onepiece">One Piece</SelectItem>
|
||||
<SelectItem value="lorcana">Lorcana</SelectItem>
|
||||
<SelectItem value="magic">Magic: The Gathering</SelectItem>
|
||||
<SelectItem value="dragonball">Dragon Ball</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gradingScale">Grading Scale</Label>
|
||||
<Select
|
||||
value={gradingScale}
|
||||
onValueChange={(value) => setGradingScale(value as 'PSA' | 'BGS' | 'CGC')}
|
||||
>
|
||||
<SelectTrigger id="gradingScale">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="PSA">PSA</SelectItem>
|
||||
<SelectItem value="BGS">BGS (Beckett)</SelectItem>
|
||||
<SelectItem value="CGC">CGC</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Grade Button */}
|
||||
<Button
|
||||
onClick={handleGrade}
|
||||
disabled={!frontImageDataUrl || isGrading || !hasGeminiKey}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
{isGrading ? (
|
||||
<>
|
||||
<Loader2 className="h-5 w-5 mr-2 animate-spin" />
|
||||
Analyzing Card{backImageDataUrl ? 's' : ''}...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Shield className="h-5 w-5 mr-2" />
|
||||
Grade Card
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Error Display */}
|
||||
{error && (
|
||||
<div className="p-4 bg-destructive/10 border border-destructive rounded-lg">
|
||||
<p className="text-sm text-destructive">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { LogIn, LogOut, UserCircle, ScanLine, BookOpen, Sparkles, Camera, Menu, Settings } from 'lucide-react';
|
||||
import { LogIn, LogOut, UserCircle, ScanLine, BookOpen, Sparkles, Camera, Menu, Settings, Award } from 'lucide-react';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { signOut } from 'firebase/auth';
|
||||
@@ -57,6 +57,11 @@ export default function Navbar() {
|
||||
<ScanLine className="mr-2 h-4 w-4" /> Scan Card
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="ghost" asChild className={mobile ? "justify-start" : ""}>
|
||||
<Link href="/dashboard/grade">
|
||||
<Award className="mr-2 h-4 w-4" /> Grade Card
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="ghost" asChild className={mobile ? "justify-start" : ""}>
|
||||
<Link href="/dashboard/generate">
|
||||
<Sparkles className="mr-2 h-4 w-4" /> Generate Card
|
||||
|
||||
@@ -81,6 +81,47 @@ export interface CardGenerationParams {
|
||||
era?: string;
|
||||
}
|
||||
|
||||
// Card Grading Types
|
||||
export interface CardGradingResult {
|
||||
overallGrade: number; // 1-10 scale (PSA standard)
|
||||
centering: {
|
||||
score: number; // 1-10
|
||||
frontCentering: string; // e.g., "55/45"
|
||||
backCentering: string; // e.g., "60/40"
|
||||
notes: string;
|
||||
};
|
||||
corners: {
|
||||
score: number; // 1-10
|
||||
condition: string; // e.g., "Sharp", "Slightly worn"
|
||||
notes: string;
|
||||
};
|
||||
edges: {
|
||||
score: number; // 1-10
|
||||
condition: string; // e.g., "Clean", "Minor wear"
|
||||
notes: string;
|
||||
};
|
||||
surface: {
|
||||
score: number; // 1-10
|
||||
condition: string; // e.g., "Near mint", "Scratches visible"
|
||||
notes: string;
|
||||
};
|
||||
gradingScale: 'PSA' | 'BGS' | 'CGC';
|
||||
gradeName: string; // e.g., "GEM MINT", "MINT", "NEAR MINT"
|
||||
estimatedValue?: string; // Optional market value estimate
|
||||
recommendations: string[]; // Suggestions for card protection/preservation
|
||||
detailedAnalysis: string; // Overall assessment
|
||||
}
|
||||
|
||||
export interface CardGradingParams {
|
||||
frontPhotoDataUri: string;
|
||||
backPhotoDataUri?: string; // Optional: back of the card
|
||||
userId?: string;
|
||||
cardName?: string; // Optional: helps with value estimation
|
||||
cardSet?: string; // Optional: helps with value estimation
|
||||
game?: TCGGame; // Optional: context for grading
|
||||
gradingScale?: 'PSA' | 'BGS' | 'CGC'; // Default to PSA
|
||||
}
|
||||
|
||||
export interface GeneratedCard {
|
||||
imageBase64: string;
|
||||
prompt: string;
|
||||
|
||||
Reference in New Issue
Block a user