mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-08-22 00:24:34 -05:00
Refactor and implement submit form functionality
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -39,3 +39,6 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# local minio storage
|
||||
.minio
|
||||
|
||||
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -3,6 +3,8 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Next.js: debug server-side",
|
||||
"preLaunchTask": "startMinio",
|
||||
"postDebugTask": "stopMinio",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "npm run dev"
|
||||
@@ -18,6 +20,8 @@
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "npm run dev",
|
||||
"preLaunchTask": "startMinio",
|
||||
"postDebugTask": "stopMinio",
|
||||
"serverReadyAction": {
|
||||
"pattern": "- Local:.+(https?://.+)",
|
||||
"uriFormat": "%s",
|
||||
|
||||
43
.vscode/tasks.json
vendored
Normal file
43
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "startMinio",
|
||||
"type": "shell",
|
||||
"command": "minio",
|
||||
"args": [
|
||||
"server",
|
||||
".minio"
|
||||
],
|
||||
"isBackground": true,
|
||||
"problemMatcher": [
|
||||
{
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": ".",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": ".",
|
||||
"endsPattern": "."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "stopMinio",
|
||||
"type": "shell",
|
||||
"command": "pkill",
|
||||
"args": [
|
||||
"-f",
|
||||
"minio"
|
||||
],
|
||||
"isBackground": true,
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
514
package-lock.json
generated
514
package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@supabase/ssr": "^0.7.0",
|
||||
"@supabase/supabase-js": "^2.74.0",
|
||||
"embla-carousel-react": "8.6.0",
|
||||
"minio": "^8.0.6",
|
||||
"next": "15.5.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
@@ -1229,6 +1230,13 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/@zxing/text-encoding": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
|
||||
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
|
||||
"license": "(Unlicense OR Apache-2.0)",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@@ -1244,6 +1252,27 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"possible-typed-array-names": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/bail": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||
@@ -1254,6 +1283,15 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/block-stream2": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz",
|
||||
"integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
@@ -1267,11 +1305,25 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/browser-or-node": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz",
|
||||
"integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
|
||||
"integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.0",
|
||||
@@ -1290,7 +1342,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -1304,7 +1355,6 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
@@ -1545,11 +1595,19 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
@@ -1599,7 +1657,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
@@ -1656,7 +1713,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1666,7 +1722,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1676,7 +1731,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
@@ -1707,12 +1761,36 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
|
||||
"integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -1726,6 +1804,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-yarn-workspace-root": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||
@@ -1736,6 +1823,21 @@
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-callable": "^1.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
@@ -1755,17 +1857,24 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/generator-function": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
|
||||
"integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
@@ -1790,7 +1899,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
@@ -1804,7 +1912,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1833,7 +1940,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
@@ -1846,7 +1952,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1855,11 +1960,25 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -1918,12 +2037,27 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/inline-style-parser": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz",
|
||||
"integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
||||
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-alphabetical": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
||||
@@ -1948,6 +2082,34 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arguments": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
|
||||
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-decimal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
|
||||
@@ -1974,6 +2136,25 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-generator-function": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
|
||||
"integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.4",
|
||||
"generator-function": "^2.0.0",
|
||||
"get-proto": "^1.0.1",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"safe-regex-test": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-hexadecimal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
|
||||
@@ -2006,6 +2187,39 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"gopd": "^1.2.0",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typed-array": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
||||
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"which-typed-array": "^1.1.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
@@ -2335,6 +2549,12 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/longest-streak": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
||||
@@ -2369,7 +2589,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -3222,6 +3441,27 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
@@ -3232,6 +3472,31 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minio": {
|
||||
"version": "8.0.6",
|
||||
"resolved": "https://registry.npmjs.org/minio/-/minio-8.0.6.tgz",
|
||||
"integrity": "sha512-sOeh2/b/XprRmEtYsnNRFtOqNRTPDvYtMWh+spWlfsuCV/+IdxNeKVUMKLqI7b5Dr07ZqCPuaRGU/rB9pZYVdQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"async": "^3.2.4",
|
||||
"block-stream2": "^2.1.0",
|
||||
"browser-or-node": "^2.1.1",
|
||||
"buffer-crc32": "^1.0.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"query-string": "^7.1.3",
|
||||
"stream-json": "^1.8.0",
|
||||
"through2": "^4.0.2",
|
||||
"web-encoding": "^1.1.5",
|
||||
"xml2js": "^0.5.0 || ^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16 || ^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
@@ -3470,6 +3735,15 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
@@ -3523,6 +3797,24 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/query-string": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
|
||||
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decode-uri-component": "^0.2.2",
|
||||
"filter-obj": "^1.1.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
@@ -3579,6 +3871,20 @@
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-gfm": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
|
||||
@@ -3658,6 +3964,49 @@
|
||||
"RomPatcher": "index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-regex-test": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
|
||||
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"is-regex": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
@@ -3681,7 +4030,6 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
@@ -3790,6 +4138,48 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-chain": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz",
|
||||
"integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stream-json": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz",
|
||||
"integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"stream-chain": "^2.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/strict-uri-encode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stringify-entities": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
||||
@@ -3804,6 +4194,18 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
|
||||
"integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/style-to-js": {
|
||||
"version": "1.1.17",
|
||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz",
|
||||
@@ -3895,6 +4297,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/through2": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
|
||||
"integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readable-stream": "3"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
@@ -4067,11 +4478,23 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"is-arguments": "^1.0.4",
|
||||
"is-generator-function": "^1.0.7",
|
||||
"is-typed-array": "^1.1.3",
|
||||
"which-typed-array": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vfile": {
|
||||
@@ -4102,6 +4525,18 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/web-encoding": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz",
|
||||
"integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"util": "^0.12.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@zxing/text-encoding": "0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
@@ -4134,6 +4569,27 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
|
||||
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"for-each": "^0.3.5",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
@@ -4155,6 +4611,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@supabase/ssr": "^0.7.0",
|
||||
"@supabase/supabase-js": "^2.74.0",
|
||||
"embla-carousel-react": "8.6.0",
|
||||
"minio": "^8.0.6",
|
||||
"next": "15.5.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
|
||||
29
src/app/api/patches/[id]/download/route.ts
Normal file
29
src/app/api/patches/[id]/download/route.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getMinioClient, PATCHES_BUCKET } from "@/utils/minio/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const supabase = await createClient();
|
||||
|
||||
const { data: patch, error } = await supabase
|
||||
.from("patches")
|
||||
.select("id, bucket, filename")
|
||||
.eq("id", Number(id))
|
||||
.maybeSingle();
|
||||
if (error || !patch) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
// Log download attempt (fire-and-forget)
|
||||
try {
|
||||
await supabase.from("patch_downloads").insert({ patch: patch.id });
|
||||
} catch {}
|
||||
|
||||
const client = getMinioClient();
|
||||
const bucket = patch.bucket || PATCHES_BUCKET;
|
||||
const url = await client.presignedGetObject(bucket, patch.filename, 60 * 5);
|
||||
return Response.redirect(url, 302);
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,61 @@ body {
|
||||
--tw-prose-captions: color-mix(in oklab, var(--foreground) 70%, transparent);
|
||||
}
|
||||
|
||||
.prose * {
|
||||
@apply m-0;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
@apply text-2xl font-bold;
|
||||
}
|
||||
.prose h1:not(:first-child) {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
@apply text-xl font-semibold;
|
||||
}
|
||||
.prose h2:not(:first-child) {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
@apply text-lg font-semibold;
|
||||
}
|
||||
.prose h3:not(:first-child) {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.prose h4, .prose h5, .prose h6 {
|
||||
@apply text-base font-semibold;
|
||||
}
|
||||
.prose h4:not(:first-child), .prose h5:not(:first-child), .prose h6:not(:first-child) {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
@apply text-base text-foreground/80;
|
||||
}
|
||||
|
||||
.prose p:not(h1 + p) {
|
||||
@apply mt-1;
|
||||
}
|
||||
.prose p:not(h2 + p) {
|
||||
@apply mt-1;
|
||||
}
|
||||
.prose p:not(h3 + p) {
|
||||
@apply mt-1;
|
||||
}
|
||||
.prose p:not(h4 + p) {
|
||||
@apply mt-1;
|
||||
}
|
||||
.prose p:not(h5 + p) {
|
||||
@apply mt-1;
|
||||
}
|
||||
.prose p:not(h6 + p) {
|
||||
@apply mt-1;
|
||||
}
|
||||
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { hacks } from "@/data/hacks";
|
||||
import { baseRoms } from "@/data/baseRoms";
|
||||
import { notFound } from "next/navigation";
|
||||
import Gallery from "@/components/Hack/Gallery";
|
||||
@@ -9,6 +8,8 @@ import remarkGfm from "remark-gfm";
|
||||
import Image from "next/image";
|
||||
import { FaDiscord, FaTwitter } from "react-icons/fa6";
|
||||
import PokeCommunityIcon from "@/components/Icons/PokeCommunityIcon";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { getMinioClient, PATCHES_BUCKET } from "@/utils/minio/server";
|
||||
|
||||
interface HackDetailProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
@@ -16,18 +17,67 @@ interface HackDetailProps {
|
||||
|
||||
export default async function HackDetail({ params }: HackDetailProps) {
|
||||
const { slug } = await params;
|
||||
const hack = hacks.find((h) => h.slug === slug) ?? notFound();
|
||||
const baseRom = baseRoms.find((r) => r.name === hack.baseRom);
|
||||
const supabase = await createClient();
|
||||
const { data: hack, error } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,version,created_at,updated_at,downloads,current_patch,box_art,social_links,created_by")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
if (error || !hack) return notFound();
|
||||
const baseRom = baseRoms.find((r) => r.id === hack.base_rom);
|
||||
|
||||
let images: string[] = [];
|
||||
const { data: covers } = await supabase
|
||||
.from("hack_covers")
|
||||
.select("url, position")
|
||||
.eq("hack_slug", slug)
|
||||
.order("position", { ascending: true });
|
||||
if (covers && covers.length > 0) {
|
||||
const { data: imagesData } = await supabase.storage
|
||||
.from('hack-covers')
|
||||
.createSignedUrls(covers.map(c => c.url), 60 * 5);
|
||||
if (imagesData) {
|
||||
images = imagesData.map(d => d.signedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
const { data: tagRows } = await supabase
|
||||
.from("hack_tags")
|
||||
.select("tags(name)")
|
||||
.eq("hack_slug", slug);
|
||||
const tags = (tagRows || []).map((r: any) => r.tags?.name).filter(Boolean) as string[];
|
||||
|
||||
const { data: profile } = await supabase
|
||||
.from("profiles")
|
||||
.select("username")
|
||||
.eq("id", hack.created_by as string)
|
||||
.maybeSingle();
|
||||
const author = profile?.username || "Unknown";
|
||||
|
||||
// Resolve a short-lived signed patch URL (if current_patch exists)
|
||||
let signedPatchUrl = "";
|
||||
if (hack.current_patch != null) {
|
||||
const { data: patch } = await supabase
|
||||
.from("patches")
|
||||
.select("id,bucket,filename")
|
||||
.eq("id", hack.current_patch as number)
|
||||
.maybeSingle();
|
||||
if (patch) {
|
||||
const client = getMinioClient();
|
||||
const bucket = patch.bucket || PATCHES_BUCKET;
|
||||
signedPatchUrl = await client.presignedGetObject(bucket, patch.filename, 60 * 5);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-screen-lg px-6 pb-28">
|
||||
<HackActions
|
||||
title={hack.title}
|
||||
version={hack.version}
|
||||
author={hack.author}
|
||||
baseRom={hack.baseRom}
|
||||
author={author}
|
||||
baseRom={baseRom?.name || ""}
|
||||
platform={baseRom?.platform}
|
||||
patchUrl={hack.patchUrl}
|
||||
patchUrl={signedPatchUrl}
|
||||
/>
|
||||
|
||||
<div className="pt-8 md:pt-10">
|
||||
@@ -39,10 +89,10 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
||||
{hack.version}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-[15px] text-foreground/70">By {hack.author}</p>
|
||||
<p className="mt-1 text-[15px] text-foreground/70">By {author}</p>
|
||||
<p className="mt-2 text-sm text-foreground/75">{hack.summary}</p>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{hack.tags.map((t) => (
|
||||
{tags.map((t) => (
|
||||
<span key={t} className="rounded-full bg-[var(--surface-2)] px-2.5 py-1 text-xs ring-1 ring-[var(--border)]">
|
||||
{t}
|
||||
</span>
|
||||
@@ -60,9 +110,9 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-col gap-6 lg:grid lg:grid-cols-[minmax(0,1fr)_340px]">
|
||||
<div className="space-y-6">
|
||||
<Gallery images={hack.covers} title={hack.title} />
|
||||
<div className="mt-6 flex flex-col gap-6 lg:grid lg:grid-cols-[minmax(0,1fr)_260px]">
|
||||
<div className="space-y-6 lg:min-w-[640px]">
|
||||
<Gallery images={images} title={hack.title} />
|
||||
|
||||
<div className="card p-5">
|
||||
<h2 className="text-xl font-semibold tracking-tight">About this hack</h2>
|
||||
@@ -76,26 +126,26 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
||||
<div className="card p-5">
|
||||
<h3 className="text-[15px] font-semibold tracking-tight">Details</h3>
|
||||
<ul className="mt-3 grid gap-2 text-sm text-foreground/75">
|
||||
<li>Base ROM: {hack.baseRom}</li>
|
||||
<li>Base ROM: {hack.base_rom}</li>
|
||||
<li>Format: BPS</li>
|
||||
<li>Created: {new Date(hack.createdAt).toLocaleDateString()}</li>
|
||||
{hack.updatedAt && (
|
||||
<li>Last updated: {new Date(hack.updatedAt).toLocaleDateString()}</li>
|
||||
<li>Created: {new Date(hack.created_at).toLocaleDateString()}</li>
|
||||
{hack.updated_at && (
|
||||
<li>Last updated: {new Date(hack.updated_at).toLocaleDateString()}</li>
|
||||
)}
|
||||
{hack.socialLinks && (
|
||||
{hack.social_links && (
|
||||
<li className="flex flex-wrap items-center justify-center gap-4 mt-4">
|
||||
{hack.socialLinks.discord && (
|
||||
<a className="underline underline-offset-2 hover:text-foreground/90 hover:scale-110 transition-transform duration-300" href={hack.socialLinks.discord} target="_blank" rel="noreferrer">
|
||||
{((hack.social_links as unknown) as { discord?: string })?.discord && (
|
||||
<a className="underline underline-offset-2 hover:text-foreground/90 hover:scale-110 transition-transform duration-300" href={((hack.social_links as unknown) as { discord?: string }).discord!} target="_blank" rel="noreferrer">
|
||||
<FaDiscord size={32} />
|
||||
</a>
|
||||
)}
|
||||
{hack.socialLinks.twitter && (
|
||||
<a className="underline underline-offset-2 hover:text-foreground/90 hover:scale-110 transition-transform duration-300" href={hack.socialLinks.twitter} target="_blank" rel="noreferrer">
|
||||
{((hack.social_links as unknown) as { twitter?: string })?.twitter && (
|
||||
<a className="underline underline-offset-2 hover:text-foreground/90 hover:scale-110 transition-transform duration-300" href={((hack.social_links as unknown) as { twitter?: string }).twitter!} target="_blank" rel="noreferrer">
|
||||
<FaTwitter size={32} />
|
||||
</a>
|
||||
)}
|
||||
{hack.socialLinks.pokecommunity && (
|
||||
<a className="underline underline-offset-2 hover:text-foreground/90 hover:scale-110 transition-transform duration-300" href={hack.socialLinks.pokecommunity} target="_blank" rel="noreferrer">
|
||||
{((hack.social_links as unknown) as { pokecommunity?: string })?.pokecommunity && (
|
||||
<a className="underline underline-offset-2 hover:text-foreground/90 hover:scale-110 transition-transform duration-300" href={((hack.social_links as unknown) as { pokecommunity?: string }).pokecommunity!} target="_blank" rel="noreferrer">
|
||||
<PokeCommunityIcon width={32} height={32} color="currentColor" />
|
||||
</a>
|
||||
)}
|
||||
@@ -103,13 +153,13 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
{hack.boxArt && (
|
||||
{hack.box_art && (
|
||||
<div className="card overflow-hidden pb-6 lg:pb-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="px-5 py-3 text-[15px] font-semibold tracking-tight">Box art</div>
|
||||
<a
|
||||
className="px-5 py-3 text-[15px] tracking-tight text-foreground/70 hover:underline"
|
||||
href={hack.boxArt}
|
||||
href={hack.box_art}
|
||||
download
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -118,7 +168,7 @@ export default async function HackDetail({ params }: HackDetailProps) {
|
||||
</a>
|
||||
</div>
|
||||
<div className="relative aspect-square w-full max-h-[340px]">
|
||||
<Image src={hack.boxArt} alt={`${hack.title} box art`} fill className="object-contain" unoptimized />
|
||||
<Image src={hack.box_art} alt={`${hack.title} box art`} fill className="object-contain" unoptimized />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
183
src/app/submit/actions.ts
Normal file
183
src/app/submit/actions.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
"use server";
|
||||
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import type { TablesInsert } from "@/types/db";
|
||||
import { getMinioClient, PATCHES_BUCKET } from "@/utils/minio/server";
|
||||
|
||||
type HackInsert = TablesInsert<"hacks">;
|
||||
|
||||
function slugify(text: string) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
async function ensureUniqueSlug(base: string, supabase: Awaited<ReturnType<typeof createClient>>) {
|
||||
let candidate = base;
|
||||
let suffix = 2;
|
||||
// Loop until slug is unique
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug")
|
||||
.eq("slug", candidate)
|
||||
.maybeSingle();
|
||||
if (error && error.code !== "PGRST116") throw error;
|
||||
if (!data) return candidate;
|
||||
candidate = `${base}-${suffix++}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareSubmission(formData: FormData) {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return { ok: false, error: "Unauthorized" } as const;
|
||||
}
|
||||
|
||||
const title = (formData.get("title") as string)?.trim();
|
||||
const summary = (formData.get("summary") as string)?.trim();
|
||||
const description = (formData.get("description") as string)?.trim();
|
||||
const base_rom = (formData.get("base_rom") as string)?.trim();
|
||||
const language = (formData.get("language") as string)?.trim();
|
||||
const version = (formData.get("version") as string)?.trim();
|
||||
const box_art = (formData.get("box_art") as string)?.trim() || null;
|
||||
const discord = (formData.get("discord") as string)?.trim();
|
||||
const twitter = (formData.get("twitter") as string)?.trim();
|
||||
const pokecommunity = (formData.get("pokecommunity") as string)?.trim();
|
||||
const tags = (formData.get("tags") as string)?.split(",").map((t) => t.trim()).filter(Boolean) || [];
|
||||
|
||||
if (!title || !summary || !description || !base_rom || !language || !version) {
|
||||
return { ok: false, error: "Missing required fields" } as const;
|
||||
}
|
||||
|
||||
const baseSlug = slugify(title);
|
||||
const slug = await ensureUniqueSlug(baseSlug, supabase);
|
||||
|
||||
const social_links: HackInsert["social_links"] =
|
||||
discord || twitter || pokecommunity
|
||||
? {
|
||||
discord: discord || undefined,
|
||||
twitter: twitter || undefined,
|
||||
pokecommunity: pokecommunity || undefined,
|
||||
}
|
||||
: null;
|
||||
|
||||
const insertPayload: HackInsert = {
|
||||
slug,
|
||||
title,
|
||||
summary,
|
||||
description,
|
||||
base_rom,
|
||||
language,
|
||||
version,
|
||||
created_by: user.id,
|
||||
downloads: 0,
|
||||
box_art,
|
||||
social_links,
|
||||
approved: false,
|
||||
patch_url: "",
|
||||
} as HackInsert;
|
||||
|
||||
const { error: insertErr } = await supabase.from("hacks").insert(insertPayload);
|
||||
if (insertErr) {
|
||||
return { ok: false, error: insertErr.message } as const;
|
||||
}
|
||||
|
||||
// Tags: restrict to existing only
|
||||
if (tags.length > 0) {
|
||||
const { data: existingTags, error: tagErr } = await supabase
|
||||
.from("tags")
|
||||
.select("id, name")
|
||||
.in("name", tags);
|
||||
if (tagErr) return { ok: false, error: tagErr.message } as const;
|
||||
if (existingTags && existingTags.length > 0) {
|
||||
const hackTags = existingTags.map((t) => ({ hack_slug: slug, tag_id: t.id }));
|
||||
const { error: htErr } = await supabase.from("hack_tags").insert(hackTags);
|
||||
if (htErr) return { ok: false, error: htErr.message } as const;
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true, slug } as const;
|
||||
}
|
||||
|
||||
export async function presignPatchAndSaveCovers(args: {
|
||||
slug: string;
|
||||
version: string;
|
||||
coverUrls: string[];
|
||||
// desired object key; if omitted we build from slug+version
|
||||
objectKey?: string;
|
||||
}) {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) return { ok: false, error: "Unauthorized" } as const;
|
||||
|
||||
// Ensure hack exists and belongs to user (created_by) to prevent spoof
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
if (!hack) return { ok: false, error: "Hack not found" } as const;
|
||||
if (hack.created_by !== user.id) return { ok: false, error: "Forbidden" } as const;
|
||||
|
||||
// Insert covers (overwrite positions)
|
||||
if (args.coverUrls && args.coverUrls.length > 0) {
|
||||
// Clear any existing rows first (idempotency on retry)
|
||||
await supabase.from("hack_covers").delete().eq("hack_slug", args.slug);
|
||||
const rows = args.coverUrls.map((url, idx) => ({ hack_slug: args.slug, url, position: idx + 1 }));
|
||||
const { error: cErr } = await supabase.from("hack_covers").insert(rows);
|
||||
if (cErr) return { ok: false, error: cErr.message } as const;
|
||||
}
|
||||
|
||||
const safeVersion = args.version.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
||||
const objectKey = args.objectKey || `${args.slug}-${safeVersion}.bps`;
|
||||
|
||||
const client = getMinioClient();
|
||||
// 10 minutes to upload
|
||||
const url = await client.presignedPutObject(PATCHES_BUCKET, objectKey, 60 * 10);
|
||||
|
||||
return { ok: true, presignedUrl: url, objectKey } as const;
|
||||
}
|
||||
|
||||
export async function confirmPatchUpload(args: { slug: string; objectKey: string; version: string }) {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) return { ok: false, error: "Unauthorized" } as const;
|
||||
|
||||
const { data: hack, error: hErr } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug, created_by")
|
||||
.eq("slug", args.slug)
|
||||
.maybeSingle();
|
||||
if (hErr) return { ok: false, error: hErr.message } as const;
|
||||
if (!hack) return { ok: false, error: "Hack not found" } as const;
|
||||
if (hack.created_by !== user.id) return { ok: false, error: "Forbidden" } as const;
|
||||
|
||||
// Create patch row
|
||||
const { data: patch, error: pErr } = await supabase
|
||||
.from("patches")
|
||||
.insert({ bucket: PATCHES_BUCKET, filename: args.objectKey, version: args.version, parent_hack: args.slug })
|
||||
.select("id")
|
||||
.single();
|
||||
if (pErr) return { ok: false, error: pErr.message } as const;
|
||||
|
||||
const { error: uErr } = await supabase
|
||||
.from("hacks")
|
||||
.update({ current_patch: patch.id })
|
||||
.eq("slug", args.slug);
|
||||
if (uErr) return { ok: false, error: uErr.message } as const;
|
||||
|
||||
return { ok: true, patchId: patch.id, redirectTo: `/hack/${args.slug}` } as const;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export default async function SubmitPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-screen-lg px-6 py-10">
|
||||
<div className="mx-auto max-w-[900px] px-6 py-10 w-full">
|
||||
<h1 className="text-3xl font-bold tracking-tight">Submit your ROM hack</h1>
|
||||
<p className="mt-2 text-[15px] text-foreground/80">Share your hack so others can discover and play it.</p>
|
||||
<div className="mt-8">
|
||||
|
||||
@@ -1,35 +1,103 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { hacks as allHacks } from "@/data/hacks";
|
||||
import HackCard from "@/components/HackCard";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
export default function DiscoverBrowser() {
|
||||
const [query, setQuery] = React.useState("");
|
||||
const [tag, setTag] = React.useState<string | null>(null);
|
||||
const [sort, setSort] = React.useState("popular");
|
||||
const [hacks, setHacks] = React.useState<any[]>([]);
|
||||
const [tags, setTags] = React.useState<string[]>([]);
|
||||
|
||||
const tags = React.useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
allHacks.forEach((h) => h.tags.forEach((t) => set.add(t)));
|
||||
return Array.from(set).sort();
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
const run = async () => {
|
||||
const supabase = createClient();
|
||||
const { data: rows } = await supabase
|
||||
.from("hacks")
|
||||
.select("slug,title,summary,description,base_rom,version,downloads,created_by,patch_url")
|
||||
.order(sort === "popular" ? "downloads" : "created_at", { ascending: false });
|
||||
const slugs = (rows || []).map((r) => r.slug);
|
||||
const { data: coverRows } = await supabase
|
||||
.from("hack_covers")
|
||||
.select("hack_slug,url,position")
|
||||
.in("hack_slug", slugs)
|
||||
.order("position", { ascending: true });
|
||||
const coversBySlug = new Map<string, string[]>();
|
||||
if (coverRows && coverRows.length > 0) {
|
||||
const { data: imagesData } = await supabase.storage
|
||||
.from('hack-covers')
|
||||
.createSignedUrls(coverRows.map(c => c.url), 60 * 5);
|
||||
if (imagesData) {
|
||||
// Map: storage object url -> signedUrl
|
||||
const urlToSignedUrl = new Map<string, string>();
|
||||
imagesData.forEach((d, idx) => {
|
||||
// If creation fails, d.signedUrl might be undefined; filter those out
|
||||
if (d.signedUrl) urlToSignedUrl.set(coverRows[idx].url, d.signedUrl);
|
||||
});
|
||||
|
||||
const hacks = React.useMemo(() => {
|
||||
let filtered = allHacks.filter((h) => {
|
||||
const q = query.toLowerCase();
|
||||
const matchesQ =
|
||||
!q ||
|
||||
coverRows.forEach((c) => {
|
||||
const arr = coversBySlug.get(c.hack_slug) || [];
|
||||
const signed = urlToSignedUrl.get(c.url);
|
||||
if (signed) {
|
||||
arr.push(signed);
|
||||
coversBySlug.set(c.hack_slug, arr);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const { data: tagRows } = await supabase
|
||||
.from("hack_tags")
|
||||
.select("hack_slug,tags(name)")
|
||||
.in("hack_slug", slugs);
|
||||
const tagsBySlug = new Map<string, string[]>();
|
||||
(tagRows || []).forEach((r: any) => {
|
||||
if (!r.tags?.name) return;
|
||||
const arr = tagsBySlug.get(r.hack_slug) || [];
|
||||
arr.push(r.tags.name);
|
||||
tagsBySlug.set(r.hack_slug, arr);
|
||||
});
|
||||
const { data: profiles } = await supabase
|
||||
.from("profiles")
|
||||
.select("id,username");
|
||||
const usernameById = new Map<string, string>();
|
||||
(profiles || []).forEach((p) => usernameById.set(p.id, p.username ? `@${p.username}` : "Unknown"));
|
||||
|
||||
const mapped = (rows || []).map((r) => ({
|
||||
slug: r.slug,
|
||||
title: r.title,
|
||||
author: usernameById.get(r.created_by as string) || "Unknown",
|
||||
covers: coversBySlug.get(r.slug) || [],
|
||||
tags: tagsBySlug.get(r.slug) || [],
|
||||
downloads: r.downloads,
|
||||
baseRomId: r.base_rom,
|
||||
version: r.version,
|
||||
summary: r.summary,
|
||||
description: r.description,
|
||||
}));
|
||||
|
||||
setHacks(mapped);
|
||||
const tagSet = new Set<string>();
|
||||
mapped.forEach((h) => h.tags.forEach((t) => tagSet.add(t)));
|
||||
setTags(Array.from(tagSet).sort());
|
||||
};
|
||||
run();
|
||||
}, [sort]);
|
||||
|
||||
const filtered = React.useMemo(() => {
|
||||
let out = hacks;
|
||||
const q = query.toLowerCase();
|
||||
if (q) {
|
||||
out = out.filter((h) =>
|
||||
h.title.toLowerCase().includes(q) ||
|
||||
h.author.toLowerCase().includes(q) ||
|
||||
h.description.toLowerCase().includes(q);
|
||||
const matchesTag = !tag || h.tags.includes(tag);
|
||||
return matchesQ && matchesTag;
|
||||
});
|
||||
if (sort === "popular") filtered = filtered.sort((a, b) => b.downloads - a.downloads);
|
||||
if (sort === "new") filtered = filtered; // mock
|
||||
return filtered;
|
||||
}, [query, tag, sort]);
|
||||
(h.description || "").toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
if (tag) out = out.filter((h) => h.tags.includes(tag));
|
||||
return out;
|
||||
}, [hacks, query, tag]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -79,7 +147,7 @@ export default function DiscoverBrowser() {
|
||||
</div>
|
||||
|
||||
<div className="mt-8 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{hacks.map((hack) => (
|
||||
{filtered.map((hack) => (
|
||||
<HackCard key={hack.slug} hack={hack} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
import Image from "next/image";
|
||||
import PixelImage from "./PixelImage";
|
||||
import Link from "next/link";
|
||||
import { formatCompactNumber } from "@/utils/format";
|
||||
import { useBaseRoms } from "@/contexts/BaseRomContext";
|
||||
import type { Hack } from "@/data/hacks";
|
||||
import { baseRoms } from "@/data/baseRoms";
|
||||
import { useEffect, useState } from "react";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { FaRegImages } from "react-icons/fa6";
|
||||
|
||||
// Using shared Hack type from data
|
||||
type CardHack = {
|
||||
slug: string;
|
||||
title: string;
|
||||
author: string;
|
||||
covers: string[];
|
||||
tags: string[];
|
||||
downloads: number;
|
||||
baseRomId?: string;
|
||||
version: string;
|
||||
summary?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export default function HackCard({ hack, clickable = true, className = "" }: { hack: Hack; clickable?: boolean; className?: string }) {
|
||||
export default function HackCard({ hack, clickable = true, className = "" }: { hack: CardHack; clickable?: boolean; className?: string }) {
|
||||
const { isLinked, hasPermission, hasCached } = useBaseRoms();
|
||||
const linked = isLinked(hack.baseRom);
|
||||
const ready = hasPermission(hack.baseRom) || hasCached(hack.baseRom);
|
||||
const match = baseRoms.find((r) => r.id === hack.baseRomId);
|
||||
const baseName = match?.name ?? undefined;
|
||||
const linked = baseName ? isLinked(baseName) : false;
|
||||
const ready = baseName ? hasPermission(baseName) || hasCached(baseName) : false;
|
||||
const images = (hack.covers && hack.covers.length > 0 ? hack.covers : []).filter(Boolean);
|
||||
const isCarousel = images.length > 1;
|
||||
const pathname = usePathname();
|
||||
@@ -30,7 +45,7 @@ export default function HackCard({ hack, clickable = true, className = "" }: { h
|
||||
emblaApi.off("select", onSelect);
|
||||
};
|
||||
}, [emblaApi]);
|
||||
const cardClass = `rounded-[12px] overflow-hidden ${
|
||||
const cardClass = `rounded-[12px] overflow-hidden min-w-[360px] ${
|
||||
clickable ? "transition-transform duration-300 hover:-translate-y-0.5 hover:shadow-xl anim-float" : ""
|
||||
} ring-1 ${ready ? "ring-emerald-400/50 bg-emerald-500/10" : "card ring-[var(--border)]"}`;
|
||||
const gradientBgClass = `bg-gradient-to-b ${ready ? 'from-emerald-300/5 to-emerald-400/30 dark:from-emerald-950/10 dark:to-emerald-600/40' : 'from-black/30 to-black/10 dark:from-black/80 dark:to-black/40'}`;
|
||||
@@ -75,7 +90,7 @@ export default function HackCard({ hack, clickable = true, className = "" }: { h
|
||||
{hack.tags.slice(0, 2).map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
className="rounded-full px-2 py-0.5 text-xs ring-1 ring-[var(--border)] bg-[var(--surface-2)] text-foreground/90 backdrop-blur-md"
|
||||
className="rounded-full px-2 py-0.5 text-xs ring-1 ring-foreground/20 dark:ring-foreground/30 bg-background/70 text-foreground/90 backdrop-blur-md"
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
@@ -141,7 +156,7 @@ export default function HackCard({ hack, clickable = true, className = "" }: { h
|
||||
return text.length > 120 ? text.slice(0, 120).trimEnd() + "…" : text;
|
||||
})()}
|
||||
</p>
|
||||
<div className="mt-3 text-xs text-foreground/60">Base: {hack.baseRom}</div>
|
||||
<div className="mt-3 text-xs text-foreground/60">Base: {baseName ?? "Unknown"}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,13 @@
|
||||
import React from "react";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import { User } from "@supabase/supabase-js";
|
||||
import { Database } from "@/types/db";
|
||||
|
||||
type Profile = Database['public']['Tables']['profiles']['Row'];
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
profile: Profile | null;
|
||||
setUser: (user: User | null) => void;
|
||||
}
|
||||
|
||||
@@ -13,6 +17,7 @@ export const AuthContext = React.createContext<AuthContextType | null>(null);
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUser] = React.useState<User | null>(null);
|
||||
const [profile, setProfile] = React.useState<Profile | null>(null);
|
||||
const supabase = createClient();
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -22,6 +27,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const { data } = await supabase.auth.getUser();
|
||||
if (!isMounted) return;
|
||||
setUser(data.user ?? null);
|
||||
if (data.user) {
|
||||
const { data: profile } = await supabase.from('profiles').select('*').eq('id', data.user.id).single();
|
||||
setProfile(profile ?? null);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUser();
|
||||
@@ -32,7 +41,11 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}, [supabase]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, setUser }}>{children}</AuthContext.Provider>
|
||||
<AuthContext.Provider value={{
|
||||
user,
|
||||
profile,
|
||||
setUser,
|
||||
}}>{children}</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
19
src/utils/minio/server.ts
Normal file
19
src/utils/minio/server.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Client } from "minio";
|
||||
|
||||
let cachedClient: Client | null = null;
|
||||
|
||||
export function getMinioClient(): Client {
|
||||
if (cachedClient) return cachedClient;
|
||||
const endPoint = process.env.R2_ENDPOINT!;
|
||||
const port = parseInt(process.env.R2_PORT!);
|
||||
const accessKey = process.env.R2_ACCESS_KEY_ID!;
|
||||
const secretKey = process.env.R2_SECRET_ACCESS_KEY!;
|
||||
const useSSL = process.env.R2_USE_SSL! === "true";
|
||||
|
||||
cachedClient = new Client({ endPoint, accessKey, secretKey, useSSL, port });
|
||||
return cachedClient;
|
||||
}
|
||||
|
||||
export const PATCHES_BUCKET = "patches";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user