Compare commits
32 Commits
236fcda671
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e67f6b073c | ||
| 569ea8833b | |||
|
|
74d83da466 | ||
| aff5fa2ea4 | |||
|
|
299f32a2b7 | ||
| 3f125e5761 | |||
|
|
fb4f50b851 | ||
| 5305dc37e7 | |||
|
|
3e5ef04c9f | ||
| e51ff24944 | |||
|
|
c138442530 | ||
| 3baec5ac0f | |||
|
|
8f84dddc64 | ||
|
|
9571980898 | ||
|
|
1ce8a6a982 | ||
|
|
f0d8297f34 | ||
|
|
f49234702c | ||
|
|
f6da7be404 | ||
|
|
c7487e37f2 | ||
|
|
873ca3e4c4 | ||
|
|
c5b3ad50ca | ||
|
|
938418cec5 | ||
|
|
5d6136a445 | ||
|
|
9a64f316b9 | ||
|
|
6199afc687 | ||
|
|
f83a7d69c8 | ||
|
|
2dd3e5b170 | ||
|
|
b9e16e2b64 | ||
|
|
7f60b4d200 | ||
|
|
6e9e014fcc | ||
| a54a74d7de | |||
| 7b95998b05 |
536
bun.lock
@@ -66,11 +66,15 @@
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.1.7",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"workbox-window": "^7.4.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@apideck/better-ajv-errors": ["@apideck/better-ajv-errors@0.3.6", "", { "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
@@ -79,34 +83,178 @@
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="],
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
|
||||
|
||||
"@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="],
|
||||
|
||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="],
|
||||
|
||||
"@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA=="],
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw=="],
|
||||
|
||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g=="],
|
||||
|
||||
"@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="],
|
||||
|
||||
"@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw=="],
|
||||
|
||||
"@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
|
||||
|
||||
"@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="],
|
||||
|
||||
"@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="],
|
||||
|
||||
"@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w=="],
|
||||
|
||||
"@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g=="],
|
||||
|
||||
"@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg=="],
|
||||
|
||||
"@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw=="],
|
||||
|
||||
"@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw=="],
|
||||
|
||||
"@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ=="],
|
||||
|
||||
"@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-replace-supers": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q=="],
|
||||
|
||||
"@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/template": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ=="],
|
||||
|
||||
"@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="],
|
||||
|
||||
"@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg=="],
|
||||
|
||||
"@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="],
|
||||
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw=="],
|
||||
|
||||
"@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="],
|
||||
|
||||
"@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg=="],
|
||||
|
||||
"@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw=="],
|
||||
|
||||
"@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="],
|
||||
|
||||
"@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="],
|
||||
|
||||
"@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="],
|
||||
|
||||
"@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw=="],
|
||||
|
||||
"@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="],
|
||||
|
||||
"@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A=="],
|
||||
|
||||
"@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.0", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ=="],
|
||||
|
||||
"@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="],
|
||||
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ=="],
|
||||
|
||||
"@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="],
|
||||
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg=="],
|
||||
|
||||
"@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA=="],
|
||||
|
||||
"@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng=="],
|
||||
|
||||
"@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ=="],
|
||||
|
||||
"@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w=="],
|
||||
|
||||
"@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="],
|
||||
|
||||
"@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg=="],
|
||||
|
||||
"@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA=="],
|
||||
|
||||
"@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog=="],
|
||||
|
||||
"@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg=="],
|
||||
|
||||
"@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw=="],
|
||||
|
||||
"@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="],
|
||||
|
||||
"@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA=="],
|
||||
|
||||
"@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="],
|
||||
|
||||
"@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="],
|
||||
|
||||
"@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q=="],
|
||||
|
||||
"@babel/preset-env": ["@babel/preset-env@7.29.0", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w=="],
|
||||
|
||||
"@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
|
||||
|
||||
"@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.29.0", "", { "dependencies": { "core-js-pure": "^3.48.0" } }, "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA=="],
|
||||
@@ -187,12 +335,16 @@
|
||||
|
||||
"@ioredis/commands": ["@ioredis/commands@1.5.0", "", {}, "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
@@ -267,6 +419,14 @@
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="],
|
||||
|
||||
"@rollup/plugin-babel": ["@rollup/plugin-babel@5.3.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.10.4", "@rollup/pluginutils": "^3.1.0" }, "peerDependencies": { "@babel/core": "^7.0.0", "@types/babel__core": "^7.1.9", "rollup": "^1.20.0||^2.0.0" }, "optionalPeers": ["@types/babel__core"] }, "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q=="],
|
||||
|
||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@15.3.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA=="],
|
||||
|
||||
"@rollup/plugin-replace": ["@rollup/plugin-replace@2.4.2", "", { "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" }, "peerDependencies": { "rollup": "^1.20.0 || ^2.0.0" } }, "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg=="],
|
||||
|
||||
"@rollup/plugin-terser": ["@rollup/plugin-terser@0.4.4", "", { "dependencies": { "serialize-javascript": "^6.0.1", "smob": "^1.0.0", "terser": "^5.17.4" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
|
||||
@@ -337,6 +497,8 @@
|
||||
|
||||
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||
|
||||
"@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="],
|
||||
|
||||
"@svgmoji/core": ["@svgmoji/core@3.2.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "emojibase": "^5.1.0", "emojibase-regex": "^5.1.1", "idb-keyval": "^5.0.2", "match-sorter": "^6.2.0", "type-fest": "^1.2.0" } }, "sha512-QsD78Op3S/5kUVsa5ierr4Wu/xwAdYuMI3Zmc/Y2ekYBEMGEUY8QxilXQRSAQ4ku4PnNV4xlB9e7xhD5hy113A=="],
|
||||
|
||||
"@svgmoji/noto": ["@svgmoji/noto@3.2.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "@svgmoji/core": "^3.2.0" } }, "sha512-JgtNciB06hMDI1Pb1N2IgLh44XRMZUUNwBANzjY5jXTPqOCu1A1VA35ENvUsRhEUZOm8I+hbdAEHkwMVqxLeIQ=="],
|
||||
@@ -511,6 +673,10 @@
|
||||
|
||||
"@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="],
|
||||
|
||||
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.3", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.2", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg=="],
|
||||
@@ -521,6 +687,8 @@
|
||||
|
||||
"agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||
|
||||
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
@@ -529,16 +697,36 @@
|
||||
|
||||
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
|
||||
|
||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||
|
||||
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
|
||||
|
||||
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.24", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
|
||||
|
||||
"babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.15", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ=="],
|
||||
|
||||
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.6", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||
@@ -549,6 +737,8 @@
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browser-image-compression": ["browser-image-compression@2.0.2", "", { "dependencies": { "uzip": "0.20201231.0" } }, "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw=="],
|
||||
@@ -561,8 +751,12 @@
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
@@ -587,6 +781,10 @@
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||
|
||||
"common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
@@ -595,6 +793,8 @@
|
||||
|
||||
"cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
|
||||
|
||||
"core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="],
|
||||
|
||||
"core-js-pure": ["core-js-pure@3.48.0", "", {}, "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw=="],
|
||||
|
||||
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
||||
@@ -607,6 +807,8 @@
|
||||
|
||||
"crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
|
||||
|
||||
"crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="],
|
||||
|
||||
"css-box-model": ["css-box-model@1.2.1", "", { "dependencies": { "tiny-invariant": "^1.0.6" } }, "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw=="],
|
||||
|
||||
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||
@@ -639,12 +841,24 @@
|
||||
|
||||
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
||||
|
||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||
|
||||
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
|
||||
|
||||
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
|
||||
|
||||
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
@@ -671,6 +885,8 @@
|
||||
|
||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||
|
||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
|
||||
|
||||
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
||||
@@ -687,6 +903,8 @@
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
@@ -695,6 +913,8 @@
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||
|
||||
"es-toolkit": ["es-toolkit@1.44.0", "", {}, "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
||||
@@ -707,6 +927,8 @@
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||
@@ -717,12 +939,22 @@
|
||||
|
||||
"fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||
|
||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||
|
||||
"frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="],
|
||||
@@ -731,30 +963,54 @@
|
||||
|
||||
"framer-motion": ["framer-motion@12.33.0", "", { "dependencies": { "motion-dom": "^12.33.0", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-ca8d+rRPcDP5iIF+MoT3WNc0KHJMjIyFAbtVLvM9eA7joGSpeqDfiNH/kCs1t4CHi04njYvWyj0jS4QlEK/rJQ=="],
|
||||
|
||||
"fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
|
||||
|
||||
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
|
||||
|
||||
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||
|
||||
"get-own-enumerable-property-symbols": ["get-own-enumerable-property-symbols@3.0.2", "", {}, "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
||||
|
||||
"glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
||||
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||
|
||||
"goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"h3-v2": ["h3@2.0.1-rc.14", "", { "dependencies": { "rou3": "^0.7.12", "srvx": "^0.11.2" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-163qbGmTr/9rqQRNuqMqtgXnOUAkE4KTdauiC9y0E5iG1I65kte9NyfWvZw5RTDMt6eY+DtyoNzrQ9wA2BfvGQ=="],
|
||||
|
||||
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
@@ -767,6 +1023,8 @@
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="],
|
||||
|
||||
"idb-keyval": ["idb-keyval@5.1.5", "", { "dependencies": { "safari-14-idb-fix": "^1.0.6" } }, "sha512-J1utxYWQokYjy01LvDQ7WmiAtZCGUSkVi9EIBfUSyLOr/BesnMIxNGASTh9A1LzeISSjSqEPsfFdTss7EE7ofQ=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
@@ -775,22 +1033,82 @@
|
||||
|
||||
"immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
||||
|
||||
"ioredis": ["ioredis@5.9.2", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ=="],
|
||||
|
||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||
|
||||
"is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
|
||||
|
||||
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
|
||||
|
||||
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
||||
|
||||
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
|
||||
|
||||
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="],
|
||||
|
||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
|
||||
|
||||
"is-generator-function": ["is-generator-function@1.1.2", "", { "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" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
||||
|
||||
"is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
|
||||
|
||||
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
||||
|
||||
"is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="],
|
||||
|
||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||
|
||||
"is-regexp": ["is-regexp@1.0.0", "", {}, "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="],
|
||||
|
||||
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
|
||||
|
||||
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
|
||||
|
||||
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
|
||||
|
||||
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
|
||||
|
||||
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
|
||||
|
||||
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
|
||||
|
||||
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
||||
|
||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"isbot": ["isbot@5.1.34", "", {}, "sha512-aCMIBSKd/XPRYdiCQTLC8QHH4YT8B3JUADu+7COgYIZPvkeoMcUHMRjZLM9/7V8fCj+l7FSREc1lOPNjzogo/A=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
|
||||
|
||||
"jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="],
|
||||
|
||||
"jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
@@ -799,8 +1117,16 @@
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
|
||||
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
|
||||
|
||||
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
@@ -811,6 +1137,8 @@
|
||||
|
||||
"klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
|
||||
|
||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
"libphonenumber-js": ["libphonenumber-js@1.12.36", "", {}, "sha512-woWhKMAVx1fzzUnMCyOzglgSgf6/AFHLASdOBcchYCyvWSGWt12imw3iu2hdI5d4dGZRsNWAmWiz37sDKUPaRQ=="],
|
||||
|
||||
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
|
||||
@@ -819,6 +1147,8 @@
|
||||
|
||||
"lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
|
||||
|
||||
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
|
||||
@@ -837,10 +1167,14 @@
|
||||
|
||||
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
|
||||
|
||||
"lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
|
||||
|
||||
"markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
|
||||
|
||||
"match-sorter": ["match-sorter@6.4.0", "", { "dependencies": { "@babel/runtime": "^7.23.8", "remove-accents": "0.5.0" } }, "sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q=="],
|
||||
@@ -853,8 +1187,12 @@
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"motion-dom": ["motion-dom@12.33.0", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-XRPebVypsl0UM+7v0Hr8o9UAj0S2djsQWRdHBd5iVouVpMrQqAI0C/rDAT3QaYnXnHuC5hMcwDHCboNeyYjPoQ=="],
|
||||
|
||||
"motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
||||
@@ -879,8 +1217,16 @@
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
||||
|
||||
"orderedmap": ["orderedmap@2.1.1", "", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="],
|
||||
|
||||
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="],
|
||||
|
||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
@@ -891,6 +1237,10 @@
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"pg": ["pg@8.18.0", "", { "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ=="],
|
||||
@@ -921,6 +1271,8 @@
|
||||
|
||||
"pocketbase": ["pocketbase@0.26.8", "", {}, "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
|
||||
@@ -949,6 +1301,8 @@
|
||||
|
||||
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
||||
|
||||
"pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="],
|
||||
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||
@@ -991,6 +1345,8 @@
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
|
||||
|
||||
"qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
|
||||
@@ -999,6 +1355,8 @@
|
||||
|
||||
"raf-schd": ["raf-schd@4.0.3", "", {}, "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="],
|
||||
|
||||
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
|
||||
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
||||
@@ -1041,12 +1399,30 @@
|
||||
|
||||
"redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||
|
||||
"regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
|
||||
|
||||
"regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="],
|
||||
|
||||
"regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="],
|
||||
|
||||
"regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="],
|
||||
|
||||
"remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
|
||||
|
||||
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
|
||||
|
||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
|
||||
@@ -1057,8 +1433,14 @@
|
||||
|
||||
"safari-14-idb-fix": ["safari-14-idb-fix@1.0.6", "", {}, "sha512-oTEQOdMwRX+uCtWCKT1nx2gAeSdpr8elg/2gcaKUH00SJU2xWESfkx11nmXwTRHy7xfQoj1o4TTQvdmuBosTnA=="],
|
||||
|
||||
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
@@ -1067,12 +1449,20 @@
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
|
||||
|
||||
"seroval": ["seroval@1.5.0", "", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="],
|
||||
|
||||
"seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||
|
||||
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
@@ -1085,8 +1475,12 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"smob": ["smob@1.6.1", "", {}, "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g=="],
|
||||
|
||||
"solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="],
|
||||
|
||||
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||
@@ -1095,6 +1489,10 @@
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"sourcemap-codec": ["sourcemap-codec@1.4.8", "", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"srvx": ["srvx@0.11.2", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-u6NbjE84IJwm1XUnJ53WqylLTQ3BdWRw03lcjBNNeMBD+EFjkl0Cnw1RVaGSqRAo38pOHOPXJH30M6cuTINUxw=="],
|
||||
@@ -1103,6 +1501,20 @@
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
|
||||
|
||||
"string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="],
|
||||
|
||||
"string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="],
|
||||
|
||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||
|
||||
"stringify-object": ["stringify-object@3.3.0", "", { "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } }, "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw=="],
|
||||
|
||||
"strip-comments": ["strip-comments@2.0.1", "", {}, "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw=="],
|
||||
|
||||
"sugarss": ["sugarss@5.0.1", "", { "peerDependencies": { "postcss": "^8.3.3" } }, "sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw=="],
|
||||
|
||||
"supertokens-js-override": ["supertokens-js-override@0.0.4", "", {}, "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg=="],
|
||||
@@ -1113,8 +1525,16 @@
|
||||
|
||||
"supertokens-website": ["supertokens-website@20.1.6", "", { "dependencies": { "browser-tabs-lock": "^1.3.0", "supertokens-js-override": "^0.0.4" } }, "sha512-WSehco2PsrFp4WY7h6tDutYyi2nPgJS8lahUadcL/cpBqgEuZ3pjnvN1NDASSNSTfXeO1smm3HrvvLZG5C7qHA=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
|
||||
|
||||
"temp-dir": ["temp-dir@2.0.0", "", {}, "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="],
|
||||
|
||||
"tempy": ["tempy@0.6.0", "", { "dependencies": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", "type-fest": "^0.16.0", "unique-string": "^2.0.0" } }, "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw=="],
|
||||
|
||||
"terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
|
||||
"tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="],
|
||||
@@ -1127,7 +1547,7 @@
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
"tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="],
|
||||
|
||||
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
||||
|
||||
@@ -1139,18 +1559,42 @@
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
||||
|
||||
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
|
||||
|
||||
"typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="],
|
||||
|
||||
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
||||
|
||||
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||
|
||||
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
|
||||
|
||||
"undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
|
||||
|
||||
"unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="],
|
||||
|
||||
"unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="],
|
||||
|
||||
"unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="],
|
||||
|
||||
"unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
|
||||
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
|
||||
"upath": ["upath@1.2.0", "", {}, "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
|
||||
@@ -1177,13 +1621,15 @@
|
||||
|
||||
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
||||
|
||||
"vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="],
|
||||
|
||||
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||
|
||||
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
"webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
@@ -1191,14 +1637,54 @@
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
"whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
|
||||
"which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
|
||||
|
||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
||||
|
||||
"which-typed-array": ["which-typed-array@1.1.20", "", { "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" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
|
||||
|
||||
"wmf": ["wmf@1.0.2", "", {}, "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="],
|
||||
|
||||
"word": ["word@0.3.0", "", {}, "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="],
|
||||
|
||||
"workbox-background-sync": ["workbox-background-sync@7.4.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.4.0" } }, "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w=="],
|
||||
|
||||
"workbox-broadcast-update": ["workbox-broadcast-update@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA=="],
|
||||
|
||||
"workbox-build": ["workbox-build@7.4.0", "", { "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", "@babel/core": "^7.24.4", "@babel/preset-env": "^7.11.0", "@babel/runtime": "^7.11.2", "@rollup/plugin-babel": "^5.2.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^2.4.1", "@rollup/plugin-terser": "^0.4.3", "@surma/rollup-plugin-off-main-thread": "^2.2.3", "ajv": "^8.6.0", "common-tags": "^1.8.0", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", "glob": "^11.0.1", "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", "rollup": "^2.79.2", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", "workbox-background-sync": "7.4.0", "workbox-broadcast-update": "7.4.0", "workbox-cacheable-response": "7.4.0", "workbox-core": "7.4.0", "workbox-expiration": "7.4.0", "workbox-google-analytics": "7.4.0", "workbox-navigation-preload": "7.4.0", "workbox-precaching": "7.4.0", "workbox-range-requests": "7.4.0", "workbox-recipes": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0", "workbox-streams": "7.4.0", "workbox-sw": "7.4.0", "workbox-window": "7.4.0" } }, "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA=="],
|
||||
|
||||
"workbox-cacheable-response": ["workbox-cacheable-response@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ=="],
|
||||
|
||||
"workbox-core": ["workbox-core@7.4.0", "", {}, "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ=="],
|
||||
|
||||
"workbox-expiration": ["workbox-expiration@7.4.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.4.0" } }, "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw=="],
|
||||
|
||||
"workbox-google-analytics": ["workbox-google-analytics@7.4.0", "", { "dependencies": { "workbox-background-sync": "7.4.0", "workbox-core": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ=="],
|
||||
|
||||
"workbox-navigation-preload": ["workbox-navigation-preload@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w=="],
|
||||
|
||||
"workbox-precaching": ["workbox-precaching@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg=="],
|
||||
|
||||
"workbox-range-requests": ["workbox-range-requests@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw=="],
|
||||
|
||||
"workbox-recipes": ["workbox-recipes@7.4.0", "", { "dependencies": { "workbox-cacheable-response": "7.4.0", "workbox-core": "7.4.0", "workbox-expiration": "7.4.0", "workbox-precaching": "7.4.0", "workbox-routing": "7.4.0", "workbox-strategies": "7.4.0" } }, "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ=="],
|
||||
|
||||
"workbox-routing": ["workbox-routing@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ=="],
|
||||
|
||||
"workbox-strategies": ["workbox-strategies@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0" } }, "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg=="],
|
||||
|
||||
"workbox-streams": ["workbox-streams@7.4.0", "", { "dependencies": { "workbox-core": "7.4.0", "workbox-routing": "7.4.0" } }, "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg=="],
|
||||
|
||||
"workbox-sw": ["workbox-sw@7.4.0", "", {}, "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw=="],
|
||||
|
||||
"workbox-window": ["workbox-window@7.4.0", "", { "dependencies": { "@types/trusted-types": "^2.0.2", "workbox-core": "7.4.0" } }, "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw=="],
|
||||
|
||||
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
||||
|
||||
"xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="],
|
||||
@@ -1217,6 +1703,14 @@
|
||||
|
||||
"@reduxjs/toolkit/immer": ["immer@11.1.3", "", {}, "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q=="],
|
||||
|
||||
"@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
|
||||
|
||||
"@rollup/plugin-babel/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="],
|
||||
|
||||
"@rollup/plugin-replace/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
|
||||
|
||||
"@rollup/plugin-replace/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@svgmoji/core/type-fest": ["type-fest@1.4.0", "", {}, "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="],
|
||||
@@ -1235,12 +1729,18 @@
|
||||
|
||||
"dotenv-expand/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
|
||||
"filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
|
||||
"jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||
|
||||
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
|
||||
"react-scan/@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="],
|
||||
@@ -1253,8 +1753,36 @@
|
||||
|
||||
"recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"supertokens-node/twilio": ["twilio@4.23.0", "", { "dependencies": { "axios": "^1.6.0", "dayjs": "^1.11.9", "https-proxy-agent": "^5.0.0", "jsonwebtoken": "^9.0.0", "qs": "^6.9.4", "scmp": "^2.1.0", "url-parse": "^1.5.9", "xmlbuilder": "^13.0.2" } }, "sha512-LdNBQfOe0dY2oJH2sAsrxazpgfFQo5yXGxe96QA8UWB5uu+433PrUbkv8gQ5RmrRCqUTPQ0aOrIyAdBr1aB03Q=="],
|
||||
|
||||
"tempy/type-fest": ["type-fest@0.16.0", "", {}, "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="],
|
||||
|
||||
"workbox-build/pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="],
|
||||
|
||||
"workbox-build/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="],
|
||||
|
||||
"workbox-build/source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="],
|
||||
|
||||
"@rollup/plugin-babel/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
|
||||
|
||||
"@rollup/plugin-babel/@rollup/pluginutils/estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="],
|
||||
|
||||
"@rollup/plugin-babel/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"@rollup/plugin-replace/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
|
||||
|
||||
"@rollup/plugin-replace/@rollup/pluginutils/estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="],
|
||||
|
||||
"@rollup/plugin-replace/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"react-scan/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
||||
|
||||
"react-scan/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
||||
@@ -1306,5 +1834,7 @@
|
||||
"react-scan/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
||||
|
||||
"react-scan/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||
|
||||
"filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,11 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: flxn-config
|
||||
key: vite_spotify_redirect_uri
|
||||
- name: COOKIE_DOMAIN
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: flxn-config
|
||||
key: cookie_domain
|
||||
|
||||
resources:
|
||||
requests:
|
||||
|
||||
@@ -12,3 +12,4 @@ data:
|
||||
vite_spotify_redirect_uri: "https://dev.flexxon.app/api/spotify/callback"
|
||||
s3_endpoint: "https://s3.yohler.net"
|
||||
s3_bucket: "flxn-dev"
|
||||
cookie_domain: "dev.flexxon.app"
|
||||
|
||||
@@ -12,3 +12,4 @@ data:
|
||||
vite_spotify_redirect_uri: "https://flexxon.app/api/spotify/callback"
|
||||
s3_endpoint: "https://s3.yohler.net"
|
||||
s3_bucket: "flxn-prod"
|
||||
cookie_domain: "flexxon.app"
|
||||
|
||||
@@ -71,6 +71,8 @@
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.1.7",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"workbox-window": "^7.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
109
pb_migrations/1771294794_created_groups.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "[a-z0-9]{15}",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 15,
|
||||
"min": 15,
|
||||
"name": "id",
|
||||
"pattern": "^[a-z0-9]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"cascadeDelete": false,
|
||||
"collectionId": "pbc_340646327",
|
||||
"hidden": false,
|
||||
"id": "relation3177167065",
|
||||
"maxSelect": 1,
|
||||
"minSelect": 0,
|
||||
"name": "tournament",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "number4113142680",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "order",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"cascadeDelete": false,
|
||||
"collectionId": "pbc_1568971955",
|
||||
"hidden": false,
|
||||
"id": "relation2529305176",
|
||||
"maxSelect": 999,
|
||||
"minSelect": 0,
|
||||
"name": "teams",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "autodate2990389176",
|
||||
"name": "created",
|
||||
"onCreate": true,
|
||||
"onUpdate": false,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "autodate3332085495",
|
||||
"name": "updated",
|
||||
"onCreate": true,
|
||||
"onUpdate": true,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3346940990",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "groups",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3346940990");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
52
pb_migrations/1771294861_updated_tournaments.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_340646327")
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("select3736761055")
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(13, new Field({
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text3736761055",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "format",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_340646327")
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(13, new Field({
|
||||
"hidden": false,
|
||||
"id": "select3736761055",
|
||||
"maxSelect": 1,
|
||||
"name": "format",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "select",
|
||||
"values": [
|
||||
"single_elim",
|
||||
"double_elim",
|
||||
"groups",
|
||||
"swiss",
|
||||
"swiss_bracket",
|
||||
"round_robin"
|
||||
]
|
||||
}))
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("text3736761055")
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
25
pb_migrations/1771294883_updated_tournaments.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_340646327")
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(14, new Field({
|
||||
"hidden": false,
|
||||
"id": "json118290348",
|
||||
"maxSize": 0,
|
||||
"name": "group_config",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_340646327")
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("json118290348")
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
29
pb_migrations/1771294898_updated_tournaments.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_340646327")
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(15, new Field({
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text2982008523",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "phase",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_340646327")
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("text2982008523")
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
47
pb_migrations/1771295070_updated_matches.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2541054544")
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(22, new Field({
|
||||
"cascadeDelete": false,
|
||||
"collectionId": "pbc_3346940990",
|
||||
"hidden": false,
|
||||
"id": "relation1841317061",
|
||||
"maxSelect": 1,
|
||||
"minSelect": 0,
|
||||
"name": "group",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
}))
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(23, new Field({
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text3987859035",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "match_type",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2541054544")
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("relation1841317061")
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("text3987859035")
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
@@ -1,17 +1,28 @@
|
||||
{
|
||||
"name": "FLXN IX",
|
||||
"short_name": "FLXN",
|
||||
"name": "FLXN",
|
||||
"description": "Amicus meus madidus",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.png",
|
||||
"src": "/icon-192x192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/favicon.png",
|
||||
"src": "/icon-512x512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"display": "standalone"
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#1e293b",
|
||||
"background_color": "#0f172a",
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"categories": ["games", "social", "beer pong"],
|
||||
"prefer_related_applications": false,
|
||||
"shortcuts": []
|
||||
}
|
||||
|
||||
BIN
public/static/img/duncer_cap_badge.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/static/img/flip_cup_badge.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
public/static/img/gets_around_badge.png
Normal file
|
After Width: | Height: | Size: 211 KiB |
BIN
public/static/img/king_of_the_hill_badge.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
public/static/img/one_up_badge.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
@@ -38,11 +38,13 @@ import { Route as AuthedAdminPreviewRouteImport } from './routes/_authed/admin/p
|
||||
import { Route as AuthedAdminBadgesRouteImport } from './routes/_authed/admin/badges'
|
||||
import { Route as AuthedAdminActivitiesRouteImport } from './routes/_authed/admin/activities'
|
||||
import { Route as AuthedAdminTournamentsIndexRouteImport } from './routes/_authed/admin/tournaments/index'
|
||||
import { Route as AuthedTournamentsIdGroupsRouteImport } from './routes/_authed/tournaments/$id.groups'
|
||||
import { Route as AuthedTournamentsIdBracketRouteImport } from './routes/_authed/tournaments/$id.bracket'
|
||||
import { Route as AuthedAdminTournamentsIdIndexRouteImport } from './routes/_authed/admin/tournaments/$id/index'
|
||||
import { Route as ApiFilesCollectionRecordIdFileRouteImport } from './routes/api/files/$collection/$recordId/$file'
|
||||
import { Route as AuthedAdminTournamentsRunIdRouteImport } from './routes/_authed/admin/tournaments/run.$id'
|
||||
import { Route as AuthedAdminTournamentsIdTeamsRouteImport } from './routes/_authed/admin/tournaments/$id/teams'
|
||||
import { Route as AuthedAdminTournamentsIdAssignPartnersRouteImport } from './routes/_authed/admin/tournaments/$id/assign-partners'
|
||||
|
||||
const RefreshSessionRoute = RefreshSessionRouteImport.update({
|
||||
id: '/refresh-session',
|
||||
@@ -191,6 +193,12 @@ const AuthedAdminTournamentsIndexRoute =
|
||||
path: '/tournaments/',
|
||||
getParentRoute: () => AuthedAdminRoute,
|
||||
} as any)
|
||||
const AuthedTournamentsIdGroupsRoute =
|
||||
AuthedTournamentsIdGroupsRouteImport.update({
|
||||
id: '/tournaments/$id/groups',
|
||||
path: '/tournaments/$id/groups',
|
||||
getParentRoute: () => AuthedRoute,
|
||||
} as any)
|
||||
const AuthedTournamentsIdBracketRoute =
|
||||
AuthedTournamentsIdBracketRouteImport.update({
|
||||
id: '/tournaments/$id/bracket',
|
||||
@@ -221,6 +229,12 @@ const AuthedAdminTournamentsIdTeamsRoute =
|
||||
path: '/tournaments/$id/teams',
|
||||
getParentRoute: () => AuthedAdminRoute,
|
||||
} as any)
|
||||
const AuthedAdminTournamentsIdAssignPartnersRoute =
|
||||
AuthedAdminTournamentsIdAssignPartnersRouteImport.update({
|
||||
id: '/tournaments/$id/assign-partners',
|
||||
path: '/tournaments/$id/assign-partners',
|
||||
getParentRoute: () => AuthedAdminRoute,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof AuthedIndexRoute
|
||||
@@ -251,7 +265,9 @@ export interface FileRoutesByFullPath {
|
||||
'/admin/': typeof AuthedAdminIndexRoute
|
||||
'/tournaments/': typeof AuthedTournamentsIndexRoute
|
||||
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
|
||||
'/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute
|
||||
'/admin/tournaments/': typeof AuthedAdminTournamentsIndexRoute
|
||||
'/admin/tournaments/$id/assign-partners': typeof AuthedAdminTournamentsIdAssignPartnersRoute
|
||||
'/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
|
||||
'/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
|
||||
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
|
||||
@@ -285,7 +301,9 @@ export interface FileRoutesByTo {
|
||||
'/admin': typeof AuthedAdminIndexRoute
|
||||
'/tournaments': typeof AuthedTournamentsIndexRoute
|
||||
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
|
||||
'/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute
|
||||
'/admin/tournaments': typeof AuthedAdminTournamentsIndexRoute
|
||||
'/admin/tournaments/$id/assign-partners': typeof AuthedAdminTournamentsIdAssignPartnersRoute
|
||||
'/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
|
||||
'/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
|
||||
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
|
||||
@@ -322,7 +340,9 @@ export interface FileRoutesById {
|
||||
'/_authed/admin/': typeof AuthedAdminIndexRoute
|
||||
'/_authed/tournaments/': typeof AuthedTournamentsIndexRoute
|
||||
'/_authed/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
|
||||
'/_authed/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute
|
||||
'/_authed/admin/tournaments/': typeof AuthedAdminTournamentsIndexRoute
|
||||
'/_authed/admin/tournaments/$id/assign-partners': typeof AuthedAdminTournamentsIdAssignPartnersRoute
|
||||
'/_authed/admin/tournaments/$id/teams': typeof AuthedAdminTournamentsIdTeamsRoute
|
||||
'/_authed/admin/tournaments/run/$id': typeof AuthedAdminTournamentsRunIdRoute
|
||||
'/api/files/$collection/$recordId/$file': typeof ApiFilesCollectionRecordIdFileRoute
|
||||
@@ -359,7 +379,9 @@ export interface FileRouteTypes {
|
||||
| '/admin/'
|
||||
| '/tournaments/'
|
||||
| '/tournaments/$id/bracket'
|
||||
| '/tournaments/$id/groups'
|
||||
| '/admin/tournaments/'
|
||||
| '/admin/tournaments/$id/assign-partners'
|
||||
| '/admin/tournaments/$id/teams'
|
||||
| '/admin/tournaments/run/$id'
|
||||
| '/api/files/$collection/$recordId/$file'
|
||||
@@ -393,7 +415,9 @@ export interface FileRouteTypes {
|
||||
| '/admin'
|
||||
| '/tournaments'
|
||||
| '/tournaments/$id/bracket'
|
||||
| '/tournaments/$id/groups'
|
||||
| '/admin/tournaments'
|
||||
| '/admin/tournaments/$id/assign-partners'
|
||||
| '/admin/tournaments/$id/teams'
|
||||
| '/admin/tournaments/run/$id'
|
||||
| '/api/files/$collection/$recordId/$file'
|
||||
@@ -429,7 +453,9 @@ export interface FileRouteTypes {
|
||||
| '/_authed/admin/'
|
||||
| '/_authed/tournaments/'
|
||||
| '/_authed/tournaments/$id/bracket'
|
||||
| '/_authed/tournaments/$id/groups'
|
||||
| '/_authed/admin/tournaments/'
|
||||
| '/_authed/admin/tournaments/$id/assign-partners'
|
||||
| '/_authed/admin/tournaments/$id/teams'
|
||||
| '/_authed/admin/tournaments/run/$id'
|
||||
| '/api/files/$collection/$recordId/$file'
|
||||
@@ -660,6 +686,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthedAdminTournamentsIndexRouteImport
|
||||
parentRoute: typeof AuthedAdminRoute
|
||||
}
|
||||
'/_authed/tournaments/$id/groups': {
|
||||
id: '/_authed/tournaments/$id/groups'
|
||||
path: '/tournaments/$id/groups'
|
||||
fullPath: '/tournaments/$id/groups'
|
||||
preLoaderRoute: typeof AuthedTournamentsIdGroupsRouteImport
|
||||
parentRoute: typeof AuthedRoute
|
||||
}
|
||||
'/_authed/tournaments/$id/bracket': {
|
||||
id: '/_authed/tournaments/$id/bracket'
|
||||
path: '/tournaments/$id/bracket'
|
||||
@@ -695,6 +728,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthedAdminTournamentsIdTeamsRouteImport
|
||||
parentRoute: typeof AuthedAdminRoute
|
||||
}
|
||||
'/_authed/admin/tournaments/$id/assign-partners': {
|
||||
id: '/_authed/admin/tournaments/$id/assign-partners'
|
||||
path: '/tournaments/$id/assign-partners'
|
||||
fullPath: '/admin/tournaments/$id/assign-partners'
|
||||
preLoaderRoute: typeof AuthedAdminTournamentsIdAssignPartnersRouteImport
|
||||
parentRoute: typeof AuthedAdminRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,6 +744,7 @@ interface AuthedAdminRouteChildren {
|
||||
AuthedAdminPreviewRoute: typeof AuthedAdminPreviewRoute
|
||||
AuthedAdminIndexRoute: typeof AuthedAdminIndexRoute
|
||||
AuthedAdminTournamentsIndexRoute: typeof AuthedAdminTournamentsIndexRoute
|
||||
AuthedAdminTournamentsIdAssignPartnersRoute: typeof AuthedAdminTournamentsIdAssignPartnersRoute
|
||||
AuthedAdminTournamentsIdTeamsRoute: typeof AuthedAdminTournamentsIdTeamsRoute
|
||||
AuthedAdminTournamentsRunIdRoute: typeof AuthedAdminTournamentsRunIdRoute
|
||||
AuthedAdminTournamentsIdIndexRoute: typeof AuthedAdminTournamentsIdIndexRoute
|
||||
@@ -715,6 +756,8 @@ const AuthedAdminRouteChildren: AuthedAdminRouteChildren = {
|
||||
AuthedAdminPreviewRoute: AuthedAdminPreviewRoute,
|
||||
AuthedAdminIndexRoute: AuthedAdminIndexRoute,
|
||||
AuthedAdminTournamentsIndexRoute: AuthedAdminTournamentsIndexRoute,
|
||||
AuthedAdminTournamentsIdAssignPartnersRoute:
|
||||
AuthedAdminTournamentsIdAssignPartnersRoute,
|
||||
AuthedAdminTournamentsIdTeamsRoute: AuthedAdminTournamentsIdTeamsRoute,
|
||||
AuthedAdminTournamentsRunIdRoute: AuthedAdminTournamentsRunIdRoute,
|
||||
AuthedAdminTournamentsIdIndexRoute: AuthedAdminTournamentsIdIndexRoute,
|
||||
@@ -735,6 +778,7 @@ interface AuthedRouteChildren {
|
||||
AuthedTournamentsTournamentIdRoute: typeof AuthedTournamentsTournamentIdRoute
|
||||
AuthedTournamentsIndexRoute: typeof AuthedTournamentsIndexRoute
|
||||
AuthedTournamentsIdBracketRoute: typeof AuthedTournamentsIdBracketRoute
|
||||
AuthedTournamentsIdGroupsRoute: typeof AuthedTournamentsIdGroupsRoute
|
||||
}
|
||||
|
||||
const AuthedRouteChildren: AuthedRouteChildren = {
|
||||
@@ -748,6 +792,7 @@ const AuthedRouteChildren: AuthedRouteChildren = {
|
||||
AuthedTournamentsTournamentIdRoute: AuthedTournamentsTournamentIdRoute,
|
||||
AuthedTournamentsIndexRoute: AuthedTournamentsIndexRoute,
|
||||
AuthedTournamentsIdBracketRoute: AuthedTournamentsIdBracketRoute,
|
||||
AuthedTournamentsIdGroupsRoute: AuthedTournamentsIdGroupsRoute,
|
||||
}
|
||||
|
||||
const AuthedRouteWithChildren =
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ensureSuperTokensFrontend } from "@/lib/supertokens/client";
|
||||
import { AuthContextType } from "@/contexts/auth-context";
|
||||
import Providers from "@/features/core/components/providers";
|
||||
import { SessionMonitor } from "@/components/session-monitor";
|
||||
import { IOSInstallPrompt } from "@/components/ios-install-prompt";
|
||||
import { ColorSchemeScript, mantineHtmlProps } from "@mantine/core";
|
||||
import { HeaderConfig } from "@/features/core/types/header-config";
|
||||
import { playerQueries } from "@/features/players/queries";
|
||||
@@ -41,30 +42,45 @@ export const Route = createRootRouteWithContext<{
|
||||
content:
|
||||
"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, interactive-widget=resizes-content",
|
||||
},
|
||||
{ property: 'og:title', content: 'FLXN IX' },
|
||||
{ property: 'og:description', content: 'Register for FLXN IX and view FLXN stats' },
|
||||
{ name: 'description', content: 'Amicus meus madidus' },
|
||||
{ name: 'keywords', content: 'FLXN, beer pong, tournament, sports, statistics, pong' },
|
||||
{ name: 'theme-color', content: '#1e293b' },
|
||||
{ property: 'og:title', content: 'FLXN' },
|
||||
{ property: 'og:description', content: 'Amicus meus madidus' },
|
||||
{ property: 'og:url', content: 'https://flexxon.app' },
|
||||
{ property: 'og:type', content: 'website' },
|
||||
{ property: 'og:site_name', content: 'FLXN IX' },
|
||||
{ property: 'og:site_name', content: 'FLXN' },
|
||||
{ property: 'og:image', content: 'https://flexxon.app/favicon.png' },
|
||||
{ property: 'og:image:width', content: '512' },
|
||||
{ property: 'og:image:height', content: '512' },
|
||||
{ property: 'og:image:alt', content: 'FLXN logo' },
|
||||
{ property: 'og:locale', content: 'en_US' },
|
||||
{ name: 'twitter:card', content: 'summary' },
|
||||
{ name: 'twitter:title', content: 'FLXN' },
|
||||
{ name: 'twitter:description', content: 'Amicus meus madidus' },
|
||||
{ name: 'twitter:image', content: 'https://flexxon.app/favicon.png' },
|
||||
{ name: 'mobile-web-app-capable', content: 'yes' },
|
||||
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||
{ name: 'apple-mobile-web-app-title', content: 'FLXN' },
|
||||
],
|
||||
links: [
|
||||
{
|
||||
rel: "apple-touch-icon",
|
||||
sizes: "180x180",
|
||||
href: "/favicon.png",
|
||||
href: "/apple-touch-icon.png",
|
||||
},
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "32x32",
|
||||
href: "/favicon.png",
|
||||
href: "/favicon-32x32.png",
|
||||
},
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "16x16",
|
||||
href: "/favicon.png",
|
||||
href: "/favicon-16x16.png",
|
||||
},
|
||||
{ rel: "manifest", href: "/site.webmanifest" },
|
||||
{ rel: "icon", href: "/favicon.ico" },
|
||||
@@ -96,23 +112,35 @@ export const Route = createRootRouteWithContext<{
|
||||
component: RootComponent,
|
||||
notFoundComponent: () => <Navigate to="/" />,
|
||||
beforeLoad: async ({ context, location }) => {
|
||||
// Skip auth check for refresh-session route to avoid infinite loops
|
||||
if (location.pathname === '/refresh-session') {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (location.pathname === '/login' || location.pathname === '/logout') {
|
||||
const publicRoutes = ['/login', '/logout', '/refresh-session'];
|
||||
if (publicRoutes.some(route => location.pathname.startsWith(route))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
// https://github.com/TanStack/router/discussions/3531
|
||||
const auth = await ensureServerQueryData(
|
||||
context.queryClient,
|
||||
playerQueries.auth()
|
||||
);
|
||||
return { auth };
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (typeof window !== 'undefined') {
|
||||
const { doesSessionExist, attemptRefreshingSession } = await import('supertokens-web-js/recipe/session');
|
||||
|
||||
const sessionExists = await doesSessionExist();
|
||||
if (sessionExists) {
|
||||
try {
|
||||
await attemptRefreshingSession();
|
||||
const auth = await ensureServerQueryData(
|
||||
context.queryClient,
|
||||
playerQueries.auth()
|
||||
);
|
||||
return { auth };
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
},
|
||||
@@ -128,6 +156,7 @@ function RootComponent() {
|
||||
<RootDocument>
|
||||
<Providers>
|
||||
<SessionMonitor />
|
||||
<IOSInstallPrompt />
|
||||
<Outlet />
|
||||
</Providers>
|
||||
</RootDocument>
|
||||
|
||||
167
src/app/routes/_authed/admin/tournaments/$id/assign-partners.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
|
||||
import { tournamentQueries, useFreeAgents, useTournament } from "@/features/tournaments/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import { Stack, Text, Button, Alert, LoadingOverlay, Group } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import useGenerateRandomTeams from "@/features/tournaments/hooks/use-generate-random-teams";
|
||||
import useConfirmTeamAssignments from "@/features/tournaments/hooks/use-confirm-team-assignments";
|
||||
import TeamAssignmentPreview from "@/features/tournaments/components/team-assignment-preview";
|
||||
import { WarningCircleIcon, ShuffleIcon, CheckCircleIcon } from "@phosphor-icons/react";
|
||||
import { PlayerInfo } from "@/features/players/types";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const Route = createFileRoute("/_authed/admin/tournaments/$id/assign-partners")({
|
||||
beforeLoad: async ({ context, params }) => {
|
||||
const { queryClient } = context;
|
||||
const tournament = await ensureServerQueryData(
|
||||
queryClient,
|
||||
tournamentQueries.details(params.id)
|
||||
);
|
||||
if (!tournament) throw redirect({ to: "/admin/tournaments" });
|
||||
return { tournament };
|
||||
},
|
||||
loader: ({ context }) => ({
|
||||
header: {
|
||||
withBackButton: true,
|
||||
title: `Manage ${context.tournament.name}`,
|
||||
},
|
||||
}),
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
interface TeamAssignment {
|
||||
player1: PlayerInfo;
|
||||
player2: PlayerInfo;
|
||||
teamName: string;
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: freeAgents } = useFreeAgents(id);
|
||||
const [assignments, setAssignments] = useState<TeamAssignment[] | null>(null);
|
||||
const [currentSeed, setCurrentSeed] = useState<number | undefined>(undefined);
|
||||
|
||||
const generateMutation = useGenerateRandomTeams();
|
||||
const confirmMutation = useConfirmTeamAssignments();
|
||||
|
||||
const hasOddPlayers = freeAgents.length % 2 !== 0;
|
||||
const hasEnoughPlayers = freeAgents.length >= 2;
|
||||
|
||||
const handleGenerate = () => {
|
||||
generateMutation.mutate(
|
||||
{ data: { tournamentId: id, seed: currentSeed } },
|
||||
{
|
||||
onSuccess: (result) => {
|
||||
setAssignments(result.assignments);
|
||||
setCurrentSeed(result.seed);
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleReroll = () => {
|
||||
if (currentSeed === undefined) return;
|
||||
generateMutation.mutate(
|
||||
{ data: { tournamentId: id, seed: currentSeed + 1 } },
|
||||
{
|
||||
onSuccess: (result) => {
|
||||
setAssignments(result.assignments);
|
||||
setCurrentSeed(result.seed);
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!assignments) return;
|
||||
|
||||
const formattedAssignments = assignments.map((a) => ({
|
||||
player1Id: a.player1.id,
|
||||
player2Id: a.player2.id,
|
||||
teamName: a.teamName,
|
||||
}));
|
||||
|
||||
confirmMutation.mutate(
|
||||
{ data: { tournamentId: id, assignments: formattedAssignments } },
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentQueries.details(id).queryKey });
|
||||
queryClient.invalidateQueries({ queryKey: tournamentQueries.free_agents(id).queryKey });
|
||||
navigate({ to: "/admin/tournaments/$id", params: { id } });
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="lg" pos="relative">
|
||||
<LoadingOverlay visible={confirmMutation.isPending} />
|
||||
|
||||
<Stack gap="xs">
|
||||
<Group gap="xs" align="baseline">
|
||||
<Text size="xl" fw={700}>
|
||||
{freeAgents.length}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{freeAgents.length === 1 ? "player enrolled" : "players enrolled"}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{!hasEnoughPlayers && (
|
||||
<Alert color="yellow" icon={<WarningCircleIcon size={16} />}>
|
||||
Need at least 2 players to create teams
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasOddPlayers && (
|
||||
<Alert color="red" icon={<WarningCircleIcon size={16} />}>
|
||||
Cannot create teams with an odd number of players. Please have one player unenroll.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!assignments && hasEnoughPlayers && !hasOddPlayers && (
|
||||
<Button
|
||||
leftSection={<ShuffleIcon size={18} />}
|
||||
onClick={handleGenerate}
|
||||
loading={generateMutation.isPending}
|
||||
>
|
||||
Generate Random Pairings
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{assignments && (
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text size="lg" fw={600}>
|
||||
Partner Assignments
|
||||
</Text>
|
||||
<Group gap="sm">
|
||||
<Button
|
||||
variant="subtle"
|
||||
leftSection={<ShuffleIcon size={16} />}
|
||||
onClick={handleReroll}
|
||||
loading={generateMutation.isPending}
|
||||
size="sm"
|
||||
>
|
||||
Re-roll
|
||||
</Button>
|
||||
<Button
|
||||
leftSection={<CheckCircleIcon size={18} />}
|
||||
onClick={handleConfirm}
|
||||
loading={confirmMutation.isPending}
|
||||
size="sm"
|
||||
>
|
||||
Confirm & Create Teams
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<TeamAssignmentPreview assignments={assignments} />
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
} from "@/features/tournaments/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import SeedTournament from "@/features/tournaments/components/seed-tournament";
|
||||
import { Container } from "@mantine/core";
|
||||
import SetupGroupStage from "@/features/tournaments/components/setup-group-stage";
|
||||
import GroupStageView from "@/features/tournaments/components/group-stage-view";
|
||||
import { Container, Stack, Divider, Title } from "@mantine/core";
|
||||
import { useMemo } from "react";
|
||||
import { BracketData } from "@/features/bracket/types";
|
||||
import { Match } from "@/features/matches/types";
|
||||
@@ -43,6 +45,20 @@ function RouteComponent() {
|
||||
const { roles } = useAuth();
|
||||
const isAdmin = roles?.includes('Admin') || false;
|
||||
|
||||
const hasGroupStage = useMemo(() => {
|
||||
return tournament.matches?.some((match) => match.round === -1) || false;
|
||||
}, [tournament.matches]);
|
||||
|
||||
const hasKnockout = useMemo(() => {
|
||||
return tournament.matches?.some((match) => match.round !== -1) || false;
|
||||
}, [tournament.matches]);
|
||||
|
||||
const knockoutBracketPopulated = useMemo(() => {
|
||||
return tournament.matches?.some((match) =>
|
||||
match.round === 0 && match.lid >= 0 && (match.home || match.away)
|
||||
) || false;
|
||||
}, [tournament.matches]);
|
||||
|
||||
const bracket: BracketData = useMemo(() => {
|
||||
if (!tournament.matches || tournament.matches.length === 0) {
|
||||
return { winners: [], losers: [] };
|
||||
@@ -52,6 +68,7 @@ function RouteComponent() {
|
||||
const losersMap = new Map<number, Match[]>();
|
||||
|
||||
tournament.matches
|
||||
.filter((match) => match.round !== -1)
|
||||
.sort((a, b) => a.lid - b.lid)
|
||||
.forEach((match) => {
|
||||
if (!match.is_losers_bracket) {
|
||||
@@ -79,15 +96,51 @@ function RouteComponent() {
|
||||
|
||||
return (
|
||||
<Container size="md" px={0}>
|
||||
{ isAdmin && <SpotifyControlsBar />}
|
||||
{ isAdmin && !tournament.regional && <SpotifyControlsBar />}
|
||||
{tournament.matches?.length ? (
|
||||
<BracketView bracket={bracket} showControls />
|
||||
hasGroupStage && hasKnockout ? (
|
||||
<Stack gap="xl">
|
||||
<GroupStageView
|
||||
groups={tournament.groups || []}
|
||||
matches={tournament.matches}
|
||||
showControls
|
||||
tournamentId={tournament.id}
|
||||
hasKnockoutBracket={knockoutBracketPopulated}
|
||||
isRegional={tournament.regional}
|
||||
groupConfig={tournament.group_config}
|
||||
/>
|
||||
<Divider />
|
||||
<div>
|
||||
<Title order={3} ta="center" mb="md">Knockout Bracket</Title>
|
||||
<BracketView bracket={bracket} showControls groupConfig={tournament.group_config} />
|
||||
</div>
|
||||
</Stack>
|
||||
) : hasGroupStage ? (
|
||||
<GroupStageView
|
||||
groups={tournament.groups || []}
|
||||
matches={tournament.matches}
|
||||
showControls
|
||||
tournamentId={tournament.id}
|
||||
hasKnockoutBracket={knockoutBracketPopulated}
|
||||
isRegional={tournament.regional}
|
||||
groupConfig={tournament.group_config}
|
||||
/>
|
||||
) : (
|
||||
<BracketView bracket={bracket} showControls groupConfig={tournament.group_config} />
|
||||
)
|
||||
) : (
|
||||
<SeedTournament
|
||||
tournamentId={tournament.id}
|
||||
teams={tournament.teams || []}
|
||||
isRegional={tournament.regional}
|
||||
/>
|
||||
tournament.regional === true ? (
|
||||
<SetupGroupStage
|
||||
tournamentId={tournament.id}
|
||||
teams={tournament.teams || []}
|
||||
/>
|
||||
) : (
|
||||
<SeedTournament
|
||||
tournamentId={tournament.id}
|
||||
teams={tournament.teams || []}
|
||||
isRegional={tournament.regional}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
useTournament,
|
||||
} from "@/features/tournaments/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import SeedTournament from "@/features/tournaments/components/seed-tournament";
|
||||
import { Container } from "@mantine/core";
|
||||
import { useMemo } from "react";
|
||||
import { BracketData } from "@/features/bracket/types";
|
||||
@@ -18,7 +17,7 @@ export const Route = createFileRoute("/_authed/tournaments/$id/bracket")({
|
||||
queryClient,
|
||||
tournamentQueries.details(params.id)
|
||||
);
|
||||
if (!tournament) throw redirect({ to: "/admin/tournaments" });
|
||||
if (!tournament) throw redirect({ to: "/tournaments" });
|
||||
return {
|
||||
tournament,
|
||||
};
|
||||
@@ -26,7 +25,6 @@ export const Route = createFileRoute("/_authed/tournaments/$id/bracket")({
|
||||
loader: ({ context }) => ({
|
||||
fullWidth: true,
|
||||
withPadding: false,
|
||||
showSpotifyPanel: true,
|
||||
header: {
|
||||
withBackButton: true,
|
||||
title: `${context.tournament.name}`,
|
||||
@@ -48,6 +46,7 @@ function RouteComponent() {
|
||||
const losersMap = new Map<number, Match[]>();
|
||||
|
||||
tournament.matches
|
||||
.filter((match) => match.round !== -1)
|
||||
.sort((a, b) => a.lid - b.lid)
|
||||
.forEach((match) => {
|
||||
if (!match.is_losers_bracket) {
|
||||
@@ -75,7 +74,7 @@ function RouteComponent() {
|
||||
|
||||
return (
|
||||
<Container size="md" px={0}>
|
||||
<BracketView bracket={bracket} />
|
||||
<BracketView bracket={bracket} groupConfig={tournament.group_config} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
47
src/app/routes/_authed/tournaments/$id.groups.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import {
|
||||
tournamentQueries,
|
||||
useTournament,
|
||||
} from "@/features/tournaments/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import GroupStageView from "@/features/tournaments/components/group-stage-view";
|
||||
import { Container } from "@mantine/core";
|
||||
|
||||
export const Route = createFileRoute("/_authed/tournaments/$id/groups")({
|
||||
beforeLoad: async ({ context, params }) => {
|
||||
const { queryClient } = context;
|
||||
const tournament = await ensureServerQueryData(
|
||||
queryClient,
|
||||
tournamentQueries.details(params.id)
|
||||
);
|
||||
if (!tournament) throw redirect({ to: "/tournaments" });
|
||||
return {
|
||||
tournament,
|
||||
};
|
||||
},
|
||||
loader: ({ context }) => ({
|
||||
fullWidth: true,
|
||||
withPadding: false,
|
||||
header: {
|
||||
withBackButton: true,
|
||||
title: `${context.tournament.name}`,
|
||||
},
|
||||
}),
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
const { data: tournament } = useTournament(id);
|
||||
|
||||
return (
|
||||
<Container size="md" px={0}>
|
||||
<GroupStageView
|
||||
groups={tournament.groups || []}
|
||||
matches={tournament.matches || []}
|
||||
isRegional={tournament.regional}
|
||||
groupConfig={tournament.group_config}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
59
src/components/ios-install-prompt.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Box, Paper, Group, Text, ActionIcon } from '@mantine/core'
|
||||
import { DownloadIcon, XIcon } from '@phosphor-icons/react'
|
||||
|
||||
export function IOSInstallPrompt() {
|
||||
const [show, setShow] = useState(false)
|
||||
const [platform, setPlatform] = useState<'ios' | 'android' | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
const isAndroid = /Android/.test(navigator.userAgent)
|
||||
|
||||
const isInStandaloneMode =
|
||||
window.matchMedia('(display-mode: standalone)').matches ||
|
||||
('standalone' in window.navigator && (window.navigator as any).standalone)
|
||||
|
||||
const hasBeenDismissed = localStorage.getItem('pwa-install-prompt-dismissed') === 'true'
|
||||
|
||||
if ((isIOS || isAndroid) && !isInStandaloneMode && !hasBeenDismissed) {
|
||||
setPlatform(isIOS ? 'ios' : 'android')
|
||||
const timer = setTimeout(() => setShow(true), 3000)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleDismiss = () => {
|
||||
localStorage.setItem('pwa-install-prompt-dismissed', 'true')
|
||||
setShow(false)
|
||||
}
|
||||
|
||||
if (!show || !platform) return null
|
||||
|
||||
const instructions = platform === 'ios'
|
||||
? 'Tap Share → Add to Home Screen'
|
||||
: 'Tap Menu (⋮) → Add to Home screen'
|
||||
|
||||
return (
|
||||
<Box style={{ position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 1000, padding: '8px' }}>
|
||||
<Paper shadow="lg" p="sm" style={{ background: 'var(--mantine-color-blue-9)', color: 'white' }}>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="xs" wrap="nowrap" style={{ flex: 1 }}>
|
||||
<DownloadIcon size={20} style={{ flexShrink: 0 }} />
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={500} style={{ lineHeight: 1.3 }}>
|
||||
Please install FLXN • This will save me Twilio credits as you won't be signed out!
|
||||
</Text>
|
||||
<Text size="xs" opacity={0.9} style={{ lineHeight: 1.2 }}>
|
||||
{instructions}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
<ActionIcon variant="subtle" color="white" onClick={handleDismiss}>
|
||||
<XIcon size={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { doesSessionExist } from 'supertokens-web-js/recipe/session';
|
||||
import { getOrCreateRefreshPromise } from '@/lib/supertokens/client';
|
||||
import { attemptRefreshingSession } from 'supertokens-web-js/recipe/session';
|
||||
import { logger } from '@/lib/supertokens';
|
||||
|
||||
export function SessionMonitor() {
|
||||
const navigate = useNavigate();
|
||||
const lastRefreshTimeRef = useRef<number>(0);
|
||||
const REFRESH_COOLDOWN = 30 * 1000;
|
||||
|
||||
@@ -49,12 +51,14 @@ export function SessionMonitor() {
|
||||
}
|
||||
};
|
||||
|
||||
handleVisibilityChange();
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
};
|
||||
}, []);
|
||||
}, [navigate]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -30,17 +30,7 @@ const TeamAvatar = ({
|
||||
const hasNoLogo = !team.logo;
|
||||
const hasTwoPlayers = team.players?.length === 2;
|
||||
|
||||
let shouldShowPlayerAvatars = false;
|
||||
|
||||
if (isRegional !== undefined) {
|
||||
shouldShowPlayerAvatars = isRegional && hasTwoPlayers && hasNoLogo;
|
||||
} else {
|
||||
const tournaments = (team as any).tournaments;
|
||||
const hasTournaments = tournaments && tournaments.length > 0;
|
||||
const allTournamentsAreRegional = hasTournaments && tournaments.every((t: any) => t.regional === true);
|
||||
|
||||
shouldShowPlayerAvatars = hasTwoPlayers && hasNoLogo && (allTournamentsAreRegional || !hasTournaments);
|
||||
}
|
||||
const shouldShowPlayerAvatars = isRegional === true && hasTwoPlayers && hasNoLogo;
|
||||
|
||||
if (shouldShowPlayerAvatars && team.players?.length === 2) {
|
||||
const playerSize = size * 0.6;
|
||||
|
||||
@@ -7,10 +7,14 @@ import { Match } from "@/features/matches/types";
|
||||
|
||||
interface BracketViewProps {
|
||||
bracket: BracketData;
|
||||
showControls?: boolean
|
||||
showControls?: boolean;
|
||||
groupConfig?: {
|
||||
num_groups: number;
|
||||
advance_per_group: number;
|
||||
};
|
||||
}
|
||||
|
||||
const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls }) => {
|
||||
const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls, groupConfig }) => {
|
||||
const height = useAppShellHeight();
|
||||
const orders = useMemo(() => {
|
||||
const map: Record<number, number> = {};
|
||||
@@ -32,14 +36,14 @@ const BracketView: React.FC<BracketViewProps> = ({ bracket, showControls }) => {
|
||||
<Text fw={600} size="md" m={16}>
|
||||
Winners Bracket
|
||||
</Text>
|
||||
<Bracket rounds={bracket.winners} orders={orders} showControls={showControls} />
|
||||
<Bracket rounds={bracket.winners} orders={orders} showControls={showControls} groupConfig={groupConfig} />
|
||||
</div>
|
||||
{bracket.losers && (
|
||||
{bracket.losers && bracket.losers.length > 0 && bracket.losers.some(round => round.length > 0) && (
|
||||
<div>
|
||||
<Text fw={600} size="md" m={16}>
|
||||
Losers Bracket
|
||||
</Text>
|
||||
<Bracket rounds={bracket.losers} orders={orders} showControls={showControls} />
|
||||
<Bracket rounds={bracket.losers} orders={orders} showControls={showControls} groupConfig={groupConfig} />
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
@@ -7,12 +7,17 @@ interface BracketProps {
|
||||
rounds: Match[][];
|
||||
orders: Record<number, number>;
|
||||
showControls?: boolean;
|
||||
groupConfig?: {
|
||||
num_groups: number;
|
||||
advance_per_group: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const Bracket: React.FC<BracketProps> = ({
|
||||
rounds,
|
||||
orders,
|
||||
showControls,
|
||||
groupConfig,
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
@@ -132,6 +137,7 @@ export const Bracket: React.FC<BracketProps> = ({
|
||||
match={match}
|
||||
orders={orders}
|
||||
showControls={showControls}
|
||||
groupConfig={groupConfig}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -17,16 +17,62 @@ interface MatchCardProps {
|
||||
match: Match;
|
||||
orders: Record<number, number>;
|
||||
showControls?: boolean;
|
||||
groupConfig?: {
|
||||
num_groups: number;
|
||||
advance_per_group: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
match,
|
||||
orders,
|
||||
showControls,
|
||||
groupConfig,
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
const editSheet = useSheet();
|
||||
const { playTrack, pause } = useSpotifyPlayback();
|
||||
|
||||
const getGroupLabel = useCallback((seed: number | undefined) => {
|
||||
if (!seed || !groupConfig) return undefined;
|
||||
|
||||
const groupNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
||||
const numGroups = groupConfig.num_groups;
|
||||
const advancePerGroup = groupConfig.advance_per_group;
|
||||
|
||||
const totalQualifiedTeams = numGroups * advancePerGroup;
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(totalQualifiedTeams)));
|
||||
const wildcardsNeeded = nextPowerOf2 - totalQualifiedTeams;
|
||||
|
||||
if (seed > totalQualifiedTeams && wildcardsNeeded > 0) {
|
||||
const wildcardNumber = seed - totalQualifiedTeams;
|
||||
return `Wildcard ${wildcardNumber}`;
|
||||
}
|
||||
|
||||
const pairIndex = Math.floor((seed - 1) / 2);
|
||||
const isFirstInPair = (seed - 1) % 2 === 0;
|
||||
|
||||
if (isFirstInPair) {
|
||||
const groupIndex = pairIndex % numGroups;
|
||||
const rankIndex = Math.floor(pairIndex / numGroups);
|
||||
|
||||
const rank = rankIndex + 1;
|
||||
const groupName = groupNames[groupIndex] || `${groupIndex + 1}`;
|
||||
const rankSuffix = rank === 1 ? '1st' : rank === 2 ? '2nd' : rank === 3 ? '3rd' : `${rank}th`;
|
||||
|
||||
return `${groupName} ${rankSuffix}`;
|
||||
} else {
|
||||
const groupIndex = (pairIndex + 1) % numGroups;
|
||||
const rankIndex = advancePerGroup - 1 - Math.floor(pairIndex / numGroups);
|
||||
|
||||
const rank = rankIndex + 1;
|
||||
const groupName = groupNames[groupIndex] || `${groupIndex + 1}`;
|
||||
const rankSuffix = rank === 1 ? '1st' : rank === 2 ? '2nd' : rank === 3 ? '3rd' : `${rank}th`;
|
||||
|
||||
return `${groupName} ${rankSuffix}`;
|
||||
}
|
||||
}, [groupConfig]);
|
||||
|
||||
const homeSlot = useMemo(
|
||||
() => ({
|
||||
from: orders[match.home_from_lid],
|
||||
@@ -39,8 +85,9 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
match.home_cups !== undefined &&
|
||||
match.away_cups !== undefined &&
|
||||
match.home_cups > match.away_cups,
|
||||
groupLabel: !match.home && match.home_seed ? getGroupLabel(match.home_seed) : undefined,
|
||||
}),
|
||||
[match]
|
||||
[match, getGroupLabel]
|
||||
);
|
||||
const awaySlot = useMemo(
|
||||
() => ({
|
||||
@@ -54,8 +101,9 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
match.away_cups !== undefined &&
|
||||
match.home_cups !== undefined &&
|
||||
match.away_cups > match.home_cups,
|
||||
groupLabel: !match.away && match.away_seed ? getGroupLabel(match.away_seed) : undefined,
|
||||
}),
|
||||
[match]
|
||||
[match, getGroupLabel]
|
||||
);
|
||||
|
||||
const showToolbar = useMemo(
|
||||
@@ -179,8 +227,11 @@ export const MatchCard: React.FC<MatchCardProps> = ({
|
||||
data: match.id,
|
||||
});
|
||||
|
||||
// Play walkout sequence after starting the match
|
||||
if (hasWalkoutData && match.home?.name && match.away?.name) {
|
||||
// Skip announcements for regional tournaments
|
||||
const isRegional = match.tournament?.regional === true;
|
||||
|
||||
// Play walkout sequence after starting the match (only for non-regional tournaments)
|
||||
if (!isRegional && hasWalkoutData && match.home?.name && match.away?.name) {
|
||||
try {
|
||||
const homeTeam = match.home as Team;
|
||||
const awayTeam = match.away as Team;
|
||||
|
||||
@@ -11,6 +11,7 @@ interface MatchSlotProps {
|
||||
seed?: number;
|
||||
cups?: number;
|
||||
isWinner?: boolean;
|
||||
groupLabel?: string;
|
||||
}
|
||||
|
||||
export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
@@ -19,7 +20,8 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
team,
|
||||
seed,
|
||||
cups,
|
||||
isWinner
|
||||
isWinner,
|
||||
groupLabel
|
||||
}) => (
|
||||
<Flex
|
||||
align="stretch"
|
||||
@@ -34,7 +36,7 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
<Flex align="center" gap={4} flex={1}>
|
||||
{team ? (
|
||||
<>
|
||||
<Text
|
||||
<Text
|
||||
size={team.name.length > 12 ? (team.name.length > 18 ? '10px' : '11px') : 'xs'}
|
||||
truncate
|
||||
style={{ minWidth: 0, flex: 1, lineHeight: "12px" }}
|
||||
@@ -43,18 +45,22 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
|
||||
</Text>
|
||||
{isWinner && (
|
||||
<CrownIcon
|
||||
size={14}
|
||||
size={14}
|
||||
weight="fill"
|
||||
style={{
|
||||
style={{
|
||||
color: 'gold',
|
||||
marginLeft: '2px',
|
||||
marginTop: '-1px',
|
||||
filter: 'drop-shadow(0 1px 1px rgba(0,0,0,0.3))',
|
||||
flexShrink: 0
|
||||
}}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : groupLabel ? (
|
||||
<Text c="dimmed" size="xs" truncate style={{ minWidth: 0, flex: 1 }}>
|
||||
{groupLabel}
|
||||
</Text>
|
||||
) : from ? (
|
||||
<Text c="dimmed" size="xs" truncate style={{ minWidth: 0, flex: 1 }}>
|
||||
{from_loser ? "Loser" : "Winner"} of Match {from}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { AppShell, ScrollArea, Stack, Group, Paper, useMantineColorScheme } from "@mantine/core";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { NavLink } from "./nav-link";
|
||||
import { useIsMobile } from "@/hooks/use-is-mobile";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { useLinks } from "../hooks/use-links";
|
||||
import { memo } from "react";
|
||||
import { useIsPWA } from "@/hooks/use-is-pwa";
|
||||
|
||||
const Navbar = () => {
|
||||
const { user, roles } = useAuth()
|
||||
const isMobile = useIsMobile();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const isPWA = useIsPWA();
|
||||
|
||||
const links = useLinks(user?.id, roles);
|
||||
|
||||
@@ -19,7 +20,7 @@ const Navbar = () => {
|
||||
// boxShadow: `5px 5px ${boxShadowColor}`, borderColor
|
||||
|
||||
if (isMobile) return (
|
||||
<Paper component='nav' role='navigation' withBorder shadow="sm" radius='lg' h='4rem' w='calc(100% - 1rem)' pos='fixed' m='0.5rem' bottom='0' style={{ zIndex: 10 }}>
|
||||
<Paper component='nav' role='navigation' withBorder shadow="sm" radius='lg' h='4rem' w='calc(100% - 1rem)' pos='fixed' m='0.5rem' bottom={isPWA ? '1rem' : '0'} style={{ zIndex: 10 }}>
|
||||
<Group gap='xs' justify='space-around' h='100%' w='100%' px={{ base: 12, sm: 0 }}>
|
||||
{links.map((link) => (
|
||||
<NavLink key={link.href} {...link} />
|
||||
|
||||
@@ -5,7 +5,7 @@ import { logger } from "@/lib/logger";
|
||||
import { z } from "zod";
|
||||
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
|
||||
import brackets from "@/features/bracket/utils";
|
||||
import { MatchInput } from "@/features/matches/types";
|
||||
import { Match, MatchInput } from "@/features/matches/types";
|
||||
import { serverEvents } from "@/lib/events/emitter";
|
||||
import { superTokensFunctionMiddleware } from "@/utils/supertokens";
|
||||
import { PlayerInfo } from "../players/types";
|
||||
@@ -164,6 +164,294 @@ export const startMatch = createServerFn()
|
||||
})
|
||||
);
|
||||
|
||||
export const populateKnockoutBracket = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
tournamentId: z.string(),
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data: { tournamentId } }) =>
|
||||
toServerResult(async () => {
|
||||
const tournament = await pbAdmin.getTournament(tournamentId);
|
||||
if (!tournament) {
|
||||
throw new Error("Tournament not found");
|
||||
}
|
||||
|
||||
if (!tournament.group_config) {
|
||||
throw new Error("Tournament must have group_config");
|
||||
}
|
||||
|
||||
return await populateKnockoutBracketInternal(tournamentId, tournament.group_config);
|
||||
})
|
||||
);
|
||||
|
||||
async function populateKnockoutBracketInternal(tournamentId: string, groupConfig: { num_groups: number; advance_per_group: number }) {
|
||||
logger.info('Populating knockout bracket', { tournamentId });
|
||||
|
||||
const groups = await pbAdmin.getGroupsByTournament(tournamentId);
|
||||
if (!groups || groups.length === 0) {
|
||||
throw new Error("No groups found for tournament");
|
||||
}
|
||||
|
||||
const qualifiedTeams: { teamId: string; groupOrder: number; rank: number }[] = [];
|
||||
|
||||
for (const group of groups) {
|
||||
logger.info('Processing group', {
|
||||
groupId: group.id,
|
||||
groupOrder: group.order,
|
||||
teamsCount: group.teams?.length,
|
||||
teams: group.teams
|
||||
});
|
||||
|
||||
const groupMatches = await pbAdmin.getMatchesByGroup(group.id);
|
||||
const completedMatches = groupMatches.filter(m => m.status === "ended");
|
||||
|
||||
const standings = new Map<string, { teamId: string; wins: number; losses: number; cups_for: number; cups_against: number; cup_differential: number }>();
|
||||
|
||||
for (const team of group.teams || []) {
|
||||
// group.teams can be either team objects or just team ID strings
|
||||
const teamId = typeof team === 'string' ? team : team.id;
|
||||
standings.set(teamId, {
|
||||
teamId,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
cups_for: 0,
|
||||
cups_against: 0,
|
||||
cup_differential: 0,
|
||||
});
|
||||
}
|
||||
|
||||
for (const match of completedMatches) {
|
||||
if (!match.home || !match.away) continue;
|
||||
|
||||
const homeStanding = standings.get(match.home.id);
|
||||
const awayStanding = standings.get(match.away.id);
|
||||
|
||||
if (homeStanding && awayStanding) {
|
||||
homeStanding.cups_for += match.home_cups;
|
||||
homeStanding.cups_against += match.away_cups;
|
||||
awayStanding.cups_for += match.away_cups;
|
||||
awayStanding.cups_against += match.home_cups;
|
||||
|
||||
if (match.home_cups > match.away_cups) {
|
||||
homeStanding.wins++;
|
||||
awayStanding.losses++;
|
||||
} else {
|
||||
awayStanding.wins++;
|
||||
homeStanding.losses++;
|
||||
}
|
||||
|
||||
homeStanding.cup_differential = homeStanding.cups_for - homeStanding.cups_against;
|
||||
awayStanding.cup_differential = awayStanding.cups_for - awayStanding.cups_against;
|
||||
}
|
||||
}
|
||||
|
||||
const sortedStandings = Array.from(standings.values()).sort((a, b) => {
|
||||
if (b.wins !== a.wins) return b.wins - a.wins;
|
||||
if (b.cup_differential !== a.cup_differential) return b.cup_differential - a.cup_differential;
|
||||
return b.cups_for - a.cups_for;
|
||||
});
|
||||
|
||||
const topTeams = sortedStandings.slice(0, groupConfig.advance_per_group);
|
||||
logger.info('Top teams from group', {
|
||||
groupId: group.id,
|
||||
topTeams: topTeams.map(t => ({ teamId: t.teamId, wins: t.wins, cupDiff: t.cup_differential }))
|
||||
});
|
||||
|
||||
topTeams.forEach((standing, index) => {
|
||||
qualifiedTeams.push({
|
||||
teamId: standing.teamId,
|
||||
groupOrder: group.order,
|
||||
rank: index + 1,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('Qualified teams', { qualifiedTeams });
|
||||
|
||||
const orderedTeamIds: string[] = [];
|
||||
const maxRank = groupConfig.advance_per_group;
|
||||
const numGroups = groupConfig.num_groups;
|
||||
|
||||
const teamsByGroup: string[][] = [];
|
||||
for (let g = 0; g < numGroups; g++) {
|
||||
teamsByGroup[g] = [];
|
||||
}
|
||||
|
||||
for (const qualified of qualifiedTeams) {
|
||||
teamsByGroup[qualified.groupOrder][qualified.rank - 1] = qualified.teamId;
|
||||
}
|
||||
|
||||
const totalQualifiedTeams = numGroups * maxRank;
|
||||
for (let i = 0; i < totalQualifiedTeams / 2; i++) {
|
||||
const group1 = i % numGroups;
|
||||
const rankIndex1 = Math.floor(i / numGroups);
|
||||
|
||||
const group2 = (i + 1) % numGroups;
|
||||
const rankIndex2 = maxRank - 1 - rankIndex1;
|
||||
|
||||
const team1 = teamsByGroup[group1]?.[rankIndex1];
|
||||
const team2 = teamsByGroup[group2]?.[rankIndex2];
|
||||
|
||||
if (team1) orderedTeamIds.push(team1);
|
||||
if (team2) orderedTeamIds.push(team2);
|
||||
}
|
||||
|
||||
const knockoutTeamCount = orderedTeamIds.length;
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(knockoutTeamCount)));
|
||||
const wildcardsNeeded = nextPowerOf2 - knockoutTeamCount;
|
||||
|
||||
if (wildcardsNeeded > 0) {
|
||||
logger.info('Wildcards needed', { knockoutTeamCount, nextPowerOf2, wildcardsNeeded });
|
||||
|
||||
const allNonQualifiedTeams: Array<{ teamId: string; wins: number; losses: number; cups_for: number; cups_against: number; cup_differential: number }> = [];
|
||||
const qualifiedTeamIds = new Set(qualifiedTeams.map(t => t.teamId));
|
||||
|
||||
for (const group of groups) {
|
||||
const groupMatches = await pbAdmin.getMatchesByGroup(group.id);
|
||||
const completedMatches = groupMatches.filter(m => m.status === "ended");
|
||||
|
||||
const standings = new Map<string, { teamId: string; wins: number; losses: number; cups_for: number; cups_against: number; cup_differential: number }>();
|
||||
|
||||
for (const team of group.teams || []) {
|
||||
const teamId = typeof team === 'string' ? team : team.id;
|
||||
|
||||
if (qualifiedTeamIds.has(teamId)) continue;
|
||||
|
||||
standings.set(teamId, {
|
||||
teamId,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
cups_for: 0,
|
||||
cups_against: 0,
|
||||
cup_differential: 0,
|
||||
});
|
||||
}
|
||||
|
||||
for (const match of completedMatches) {
|
||||
if (!match.home || !match.away) continue;
|
||||
|
||||
const homeStanding = standings.get(match.home.id);
|
||||
const awayStanding = standings.get(match.away.id);
|
||||
|
||||
if (homeStanding) {
|
||||
homeStanding.cups_for += match.home_cups;
|
||||
homeStanding.cups_against += match.away_cups;
|
||||
homeStanding.cup_differential = homeStanding.cups_for - homeStanding.cups_against;
|
||||
|
||||
if (match.home_cups > match.away_cups) {
|
||||
homeStanding.wins++;
|
||||
} else {
|
||||
homeStanding.losses++;
|
||||
}
|
||||
}
|
||||
|
||||
if (awayStanding) {
|
||||
awayStanding.cups_for += match.away_cups;
|
||||
awayStanding.cups_against += match.home_cups;
|
||||
awayStanding.cup_differential = awayStanding.cups_for - awayStanding.cups_against;
|
||||
|
||||
if (match.away_cups > match.home_cups) {
|
||||
awayStanding.wins++;
|
||||
} else {
|
||||
awayStanding.losses++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allNonQualifiedTeams.push(...Array.from(standings.values()));
|
||||
}
|
||||
|
||||
allNonQualifiedTeams.sort((a, b) => {
|
||||
if (b.wins !== a.wins) return b.wins - a.wins;
|
||||
if (b.cup_differential !== a.cup_differential) return b.cup_differential - a.cup_differential;
|
||||
if (b.cups_for !== a.cups_for) return b.cups_for - a.cups_for;
|
||||
return a.teamId.localeCompare(b.teamId);
|
||||
});
|
||||
|
||||
const wildcardTeams = allNonQualifiedTeams.slice(0, wildcardsNeeded);
|
||||
const wildcardTeamIds = wildcardTeams.map(t => t.teamId);
|
||||
|
||||
orderedTeamIds.push(...wildcardTeamIds);
|
||||
|
||||
logger.info('Added wildcard teams', {
|
||||
wildcardTeams: wildcardTeams.map(t => ({
|
||||
teamId: t.teamId,
|
||||
wins: t.wins,
|
||||
cupDiff: t.cup_differential,
|
||||
cupsFor: t.cups_for
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('Ordered team IDs (with wildcards)', { orderedTeamIds, totalTeams: orderedTeamIds.length });
|
||||
|
||||
const tournament = await pbAdmin.getTournament(tournamentId);
|
||||
const knockoutMatches = (tournament.matches || [])
|
||||
.filter((m: Match) => m.round >= 0 && m.lid >= 0)
|
||||
.sort((a: Match, b: Match) => a.lid - b.lid);
|
||||
|
||||
const seedToTeamId = new Map<number, string>();
|
||||
orderedTeamIds.forEach((teamId, index) => {
|
||||
seedToTeamId.set(index + 1, teamId);
|
||||
});
|
||||
|
||||
logger.info('Seed to team mapping', {
|
||||
seedToTeamId: Array.from(seedToTeamId.entries()),
|
||||
orderedTeamIds
|
||||
});
|
||||
|
||||
let updatedCount = 0;
|
||||
for (const match of knockoutMatches) {
|
||||
if (match.round === 0) {
|
||||
const updates: any = {};
|
||||
|
||||
if (match.home_seed) {
|
||||
const teamId = seedToTeamId.get(match.home_seed);
|
||||
logger.info('Looking up home seed', {
|
||||
matchId: match.id,
|
||||
home_seed: match.home_seed,
|
||||
teamId
|
||||
});
|
||||
if (teamId) {
|
||||
updates.home = teamId;
|
||||
}
|
||||
}
|
||||
|
||||
if (match.away_seed) {
|
||||
const teamId = seedToTeamId.get(match.away_seed);
|
||||
logger.info('Looking up away seed', {
|
||||
matchId: match.id,
|
||||
away_seed: match.away_seed,
|
||||
teamId
|
||||
});
|
||||
if (teamId) {
|
||||
updates.away = teamId;
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.home && updates.away) {
|
||||
updates.status = "ready";
|
||||
} else if (updates.home || updates.away) {
|
||||
updates.status = "tbd";
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
logger.info('Updating match', { matchId: match.id, updates });
|
||||
await pbAdmin.updateMatch(match.id, updates);
|
||||
updatedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Updated matches', { updatedCount, totalKnockoutMatches: knockoutMatches.length });
|
||||
|
||||
await pbAdmin.updateTournament(tournamentId, {
|
||||
phase: "knockout"
|
||||
});
|
||||
|
||||
logger.info('Knockout bracket populated successfully', { tournamentId });
|
||||
}
|
||||
|
||||
const endMatchSchema = z.object({
|
||||
matchId: z.string(),
|
||||
home_cups: z.number(),
|
||||
@@ -190,19 +478,25 @@ export const endMatch = createServerFn()
|
||||
ot_count,
|
||||
});
|
||||
|
||||
if (match.lid === -1) {
|
||||
serverEvents.emit("match", {
|
||||
type: "match",
|
||||
matchId: match.id,
|
||||
tournamentId: match.tournament.id
|
||||
});
|
||||
return match;
|
||||
}
|
||||
|
||||
const matchWinner = home_cups > away_cups ? match.home : match.away;
|
||||
const matchLoser = home_cups < away_cups ? match.home : match.away;
|
||||
if (!matchWinner || !matchLoser) throw new Error("Something went wrong");
|
||||
|
||||
// winner -> where to send match winner to, loser same
|
||||
const { winner, loser } = await pbAdmin.getChildMatches(matchId);
|
||||
|
||||
// reset match check
|
||||
if (winner && winner.reset) {
|
||||
const awayTeamWon = match.away === matchWinner;
|
||||
|
||||
if (!awayTeamWon) {
|
||||
// Reset match is not necessary
|
||||
logger.info("Deleting reset match", {
|
||||
resetMatchId: winner.id,
|
||||
currentMatchId: match.id,
|
||||
@@ -214,7 +508,6 @@ export const endMatch = createServerFn()
|
||||
}
|
||||
}
|
||||
|
||||
// advance bracket
|
||||
if (winner) {
|
||||
await pbAdmin.updateMatch(winner.id, {
|
||||
[winner.home_from_lid === match.lid ? "home" : "away"]: matchWinner.id,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TeamInfo, Team } from "../teams/types";
|
||||
import { TournamentInfo } from "../tournaments/types";
|
||||
|
||||
export type MatchStatus = "tbd" | "ready" | "started" | "ended";
|
||||
export type MatchType = "group_stage" | "knockout" | "winners" | "losers" | "bracket";
|
||||
|
||||
export interface Match {
|
||||
id: string;
|
||||
@@ -29,6 +30,8 @@ export interface Match {
|
||||
updated: string;
|
||||
home_seed?: number;
|
||||
away_seed?: number;
|
||||
match_type?: MatchType;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
export const matchInputSchema = z.object({
|
||||
@@ -53,6 +56,8 @@ export const matchInputSchema = z.object({
|
||||
away: z.string().min(1).optional(),
|
||||
home_seed: z.number().int().min(1).optional(),
|
||||
away_seed: z.number().int().min(1).optional(),
|
||||
match_type: z.enum(["group_stage", "knockout", "winners", "losers", "bracket"]).optional(),
|
||||
group: z.string().optional(),
|
||||
});
|
||||
|
||||
export type MatchInput = z.infer<typeof matchInputSchema>;
|
||||
|
||||
59
src/features/teams/components/regional-team-card.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
Paper,
|
||||
Text,
|
||||
Group,
|
||||
Box,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import { useTeam } from "../queries";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
|
||||
interface RegionalTeamCardProps {
|
||||
teamId: string;
|
||||
}
|
||||
|
||||
const RegionalTeamCard = ({ teamId }: RegionalTeamCardProps) => {
|
||||
const { data: team, error } = useTeam(teamId);
|
||||
|
||||
if (error || !team) {
|
||||
return (
|
||||
<Paper p="sm" withBorder radius="md">
|
||||
<Text c="red" ta="center" size="sm">
|
||||
Failed to load team
|
||||
</Text>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper
|
||||
withBorder
|
||||
radius="lg"
|
||||
shadow="xs"
|
||||
p="xs"
|
||||
>
|
||||
<Group gap="md" align="center">
|
||||
<TeamAvatar
|
||||
team={team}
|
||||
size={40}
|
||||
radius="md"
|
||||
isRegional={true}
|
||||
style={{
|
||||
backgroundColor: team.primary_color || undefined,
|
||||
color: team.accent_color || undefined,
|
||||
}}
|
||||
/>
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Title order={5} lineClamp={1}>
|
||||
{team.name}
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" lineClamp={1}>
|
||||
{team.players?.map(p => `${p.first_name} ${p.last_name}`).join(', ')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegionalTeamCard;
|
||||
@@ -49,9 +49,10 @@ interface TeamListProps {
|
||||
teams: TeamInfo[];
|
||||
loading?: boolean;
|
||||
onTeamClick?: (teamId: string) => void;
|
||||
isRegional?: boolean;
|
||||
}
|
||||
|
||||
const TeamList = ({ teams, loading = false, onTeamClick }: TeamListProps) => {
|
||||
const TeamList = ({ teams, loading = false, onTeamClick, isRegional }: TeamListProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClick = useCallback(
|
||||
@@ -92,6 +93,7 @@ const TeamList = ({ teams, loading = false, onTeamClick }: TeamListProps) => {
|
||||
team={team}
|
||||
radius="sm"
|
||||
size={40}
|
||||
isRegional={isRegional}
|
||||
/>
|
||||
}
|
||||
style={{ cursor: "pointer" }}
|
||||
|
||||
@@ -51,6 +51,7 @@ export const teamInputSchema = z
|
||||
song_start: z.number().int().optional(),
|
||||
song_end: z.number().int().optional(),
|
||||
song_image_url: z.url("Invalid song image URL").optional(),
|
||||
private: z.boolean().optional(),
|
||||
players: z.array(z.string()).min(1, "At least one player is required").max(10, "Maximum 10 players allowed"),
|
||||
})
|
||||
.refine(
|
||||
|
||||
162
src/features/tournaments/components/edit-enrolled-players.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import {
|
||||
Stack,
|
||||
ActionIcon,
|
||||
Text,
|
||||
Group,
|
||||
Loader,
|
||||
} from "@mantine/core";
|
||||
import { TrashIcon } from "@phosphor-icons/react";
|
||||
import { useCallback, memo } from "react";
|
||||
import { useFreeAgents } from "../queries";
|
||||
import PlayerAvatar from "@/components/player-avatar";
|
||||
import { PlayerInfo, Player } from "@/features/players/types";
|
||||
import Typeahead, { TypeaheadOption } from "@/components/typeahead";
|
||||
import { usePlayers } from "@/features/players/queries";
|
||||
import useAdminEnrollPlayer from "../hooks/use-admin-enroll-player";
|
||||
import useAdminUnenrollPlayer from "../hooks/use-admin-unenroll-player";
|
||||
|
||||
interface EditEnrolledPlayersProps {
|
||||
tournamentId: string;
|
||||
}
|
||||
|
||||
interface PlayerItemProps {
|
||||
player: PlayerInfo;
|
||||
onRemove: (playerId: string) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const PlayerItem = memo(({ player, onRemove, disabled }: PlayerItemProps) => {
|
||||
return (
|
||||
<Group py="xs" px="sm" w="100%" gap="sm" align="center">
|
||||
<PlayerAvatar
|
||||
name={`${player.first_name} ${player.last_name}`}
|
||||
size={32}
|
||||
/>
|
||||
<Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text fw={500} truncate>
|
||||
{player.first_name} {player.last_name}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
onClick={() => onRemove(player.id)}
|
||||
disabled={disabled}
|
||||
size="sm"
|
||||
>
|
||||
<TrashIcon size={14} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
);
|
||||
});
|
||||
|
||||
const EditEnrolledPlayers = ({ tournamentId }: EditEnrolledPlayersProps) => {
|
||||
const { data: freeAgents = [], isLoading } = useFreeAgents(tournamentId);
|
||||
const { data: allPlayers = [] } = usePlayers();
|
||||
|
||||
const { mutate: removeFreeAgent, isPending: isRemoving } = useAdminUnenrollPlayer();
|
||||
const { mutate: enrollPlayer, isPending: isEnrolling } = useAdminEnrollPlayer();
|
||||
|
||||
const handleRemovePlayer = useCallback(
|
||||
(playerId: string) => {
|
||||
removeFreeAgent({ tournamentId, playerId });
|
||||
},
|
||||
[removeFreeAgent, tournamentId]
|
||||
);
|
||||
|
||||
const handleEnrollPlayer = useCallback(
|
||||
(option: TypeaheadOption<Player>) => {
|
||||
enrollPlayer({ tournamentId, playerId: option.data.id });
|
||||
},
|
||||
[enrollPlayer, tournamentId]
|
||||
);
|
||||
|
||||
const enrolledPlayers = freeAgents.map(agent => agent.player).filter((p): p is PlayerInfo => p !== undefined);
|
||||
const enrolledPlayerIds = new Set(enrolledPlayers.map(p => p.id));
|
||||
const hasEnrolledPlayers = enrolledPlayers.length > 0;
|
||||
|
||||
const searchPlayers = async (query: string): Promise<TypeaheadOption<Player>[]> => {
|
||||
if (!query.trim()) return [];
|
||||
|
||||
const filtered = allPlayers.filter((player: Player) => {
|
||||
const fullName = `${player.first_name} ${player.last_name}`.toLowerCase();
|
||||
return fullName.includes(query.toLowerCase()) && !enrolledPlayerIds.has(player.id);
|
||||
});
|
||||
|
||||
return filtered.map((player: Player) => ({
|
||||
id: player.id,
|
||||
data: player
|
||||
}));
|
||||
};
|
||||
|
||||
const renderPlayerOption = (option: TypeaheadOption<Player>) => {
|
||||
const player = option.data;
|
||||
return (
|
||||
<Group py="xs" px="sm" gap="sm" align="center">
|
||||
<PlayerAvatar
|
||||
name={`${player.first_name} ${player.last_name}`}
|
||||
size={32}
|
||||
/>
|
||||
<Text fw={500} truncate>
|
||||
{player.first_name} {player.last_name}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
const formatPlayer = (option: TypeaheadOption<Player>) => {
|
||||
return `${option.data.first_name} ${option.data.last_name}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="lg" w="100%">
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} size="sm">
|
||||
Add Player
|
||||
</Text>
|
||||
<Typeahead
|
||||
placeholder="Search for players to enroll..."
|
||||
onSelect={handleEnrollPlayer}
|
||||
searchFn={searchPlayers}
|
||||
renderOption={renderPlayerOption}
|
||||
format={formatPlayer}
|
||||
disabled={isEnrolling}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between">
|
||||
<Text fw={600} size="sm">
|
||||
Enrolled Players
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{enrolledPlayers.length} players
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="md">
|
||||
<Loader size="sm" />
|
||||
</Group>
|
||||
) : !hasEnrolledPlayers ? (
|
||||
<Text size="sm" c="dimmed" ta="center" py="lg">
|
||||
No players enrolled yet
|
||||
</Text>
|
||||
) : (
|
||||
<Stack gap="xs" w="100%">
|
||||
{enrolledPlayers.map((player) => (
|
||||
<PlayerItem
|
||||
key={player.id}
|
||||
player={player}
|
||||
onRemove={handleRemovePlayer}
|
||||
disabled={isRemoving}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditEnrolledPlayers;
|
||||
201
src/features/tournaments/components/group-match-card.tsx
Normal file
@@ -0,0 +1,201 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { Card, Group, Stack, Text, ActionIcon, Indicator, Flex, Box } from "@mantine/core";
|
||||
import { PlayIcon, PencilIcon } from "@phosphor-icons/react";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { endMatch, startMatch } from "@/features/matches/server";
|
||||
import { tournamentKeys } from "@/features/tournaments/queries";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { MatchForm } from "@/features/bracket/components/match-form";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
|
||||
interface GroupMatchCardProps {
|
||||
match: Match;
|
||||
showControls?: boolean;
|
||||
}
|
||||
|
||||
const GroupMatchCard: React.FC<GroupMatchCardProps> = ({ match, showControls }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const editSheet = useSheet();
|
||||
|
||||
const isReady = match.status === "ready";
|
||||
const isStarted = match.status === "started";
|
||||
const isEnded = match.status === "ended";
|
||||
|
||||
const homeWon = isEnded && match.home_cups !== undefined && match.away_cups !== undefined && match.home_cups > match.away_cups;
|
||||
const awayWon = isEnded && match.away_cups !== undefined && match.home_cups !== undefined && match.away_cups > match.home_cups;
|
||||
|
||||
const start = useServerMutation({
|
||||
mutationFn: startMatch,
|
||||
successMessage: "Match started!",
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: tournamentKeys.details(match.tournament.id),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const end = useServerMutation({
|
||||
mutationFn: endMatch,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: tournamentKeys.details(match.tournament.id),
|
||||
});
|
||||
editSheet.close();
|
||||
},
|
||||
});
|
||||
|
||||
const handleFormSubmit = useCallback(
|
||||
async (data: {
|
||||
home_cups: number;
|
||||
away_cups: number;
|
||||
ot_count: number;
|
||||
}) => {
|
||||
end.mutate({
|
||||
data: {
|
||||
...data,
|
||||
matchId: match.id,
|
||||
},
|
||||
});
|
||||
},
|
||||
[end, match.id]
|
||||
);
|
||||
|
||||
const handleStartMatch = () => {
|
||||
start.mutate({ data: match.id });
|
||||
};
|
||||
|
||||
const showStartButton = isReady && showControls;
|
||||
const showEditButton = isStarted && showControls;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex direction="row" align="stretch">
|
||||
<Indicator
|
||||
inline
|
||||
processing={isStarted}
|
||||
color="red"
|
||||
size={16}
|
||||
disabled={!isStarted || showEditButton}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<Card
|
||||
withBorder
|
||||
radius="md"
|
||||
p="md"
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Group gap="sm" style={{ flex: 1, minWidth: 0 }}>
|
||||
{match.home && <TeamAvatar team={match.home} size={32} radius="sm" isRegional={match.tournament.regional} />}
|
||||
<Text
|
||||
size="sm"
|
||||
fw={homeWon ? 700 : 500}
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
lineClamp={1}
|
||||
>
|
||||
{match.home?.name || "TBD"}
|
||||
</Text>
|
||||
</Group>
|
||||
{isEnded && match.home_cups !== undefined && (
|
||||
<Text
|
||||
size="xl"
|
||||
fw={700}
|
||||
c={homeWon ? "green" : "dimmed"}
|
||||
style={{ minWidth: 32, textAlign: 'center' }}
|
||||
>
|
||||
{match.home_cups}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Box
|
||||
style={{
|
||||
height: 1,
|
||||
backgroundColor: 'var(--mantine-color-default-border)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Group gap="sm" style={{ flex: 1, minWidth: 0 }}>
|
||||
{match.away && <TeamAvatar team={match.away} size={32} radius="sm" isRegional={match.tournament.regional} />}
|
||||
<Text
|
||||
size="sm"
|
||||
fw={awayWon ? 700 : 500}
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
lineClamp={1}
|
||||
>
|
||||
{match.away?.name || "TBD"}
|
||||
</Text>
|
||||
</Group>
|
||||
{isEnded && match.away_cups !== undefined && (
|
||||
<Text
|
||||
size="xl"
|
||||
fw={700}
|
||||
c={awayWon ? "green" : "dimmed"}
|
||||
style={{ minWidth: 32, textAlign: 'center' }}
|
||||
>
|
||||
{match.away_cups}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Indicator>
|
||||
|
||||
{showStartButton && (
|
||||
<ActionIcon
|
||||
color="green"
|
||||
onClick={handleStartMatch}
|
||||
loading={start.isPending}
|
||||
size="md"
|
||||
h="100%"
|
||||
radius="sm"
|
||||
ml={-4}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
>
|
||||
<PlayIcon size={16} weight="fill" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
|
||||
{showEditButton && (
|
||||
<ActionIcon
|
||||
color="blue"
|
||||
onClick={editSheet.open}
|
||||
size="md"
|
||||
h="100%"
|
||||
radius="sm"
|
||||
ml={-4}
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
>
|
||||
<PencilIcon size={16} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{showControls && (
|
||||
<Sheet title="End Match" opened={editSheet.isOpen} onChange={editSheet.toggle}>
|
||||
<MatchForm
|
||||
match={match}
|
||||
onSubmit={handleFormSubmit}
|
||||
onCancel={editSheet.close}
|
||||
/>
|
||||
</Sheet>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupMatchCard;
|
||||
50
src/features/tournaments/components/group-preview.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Stack, Text, Card, Group, Box } from "@mantine/core";
|
||||
import { TeamInfo } from "@/features/teams/types";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
|
||||
interface GroupAssignment {
|
||||
groupIndex: number;
|
||||
groupName: string;
|
||||
teams: TeamInfo[];
|
||||
}
|
||||
|
||||
interface GroupPreviewProps {
|
||||
groups: GroupAssignment[];
|
||||
}
|
||||
|
||||
const GroupPreview: React.FC<GroupPreviewProps> = ({ groups }) => {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
{groups.map((group) => (
|
||||
<Card key={group.groupIndex} withBorder radius="md" p="md" w="fit-content">
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs" align="center">
|
||||
<Text fw={600} size="sm">
|
||||
Group {group.groupName}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
({group.teams.length} teams)
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Stack gap="xs">
|
||||
{group.teams.map((team, index) => (
|
||||
<Group key={team.id} gap="sm" align="center" wrap="nowrap">
|
||||
<Text size="xs" c="dimmed" w={20} ta="right">
|
||||
{index + 1}
|
||||
</Text>
|
||||
<TeamAvatar team={team} size={24} radius="sm" isRegional />
|
||||
<Text size="sm" truncate style={{ flex: 1 }}>
|
||||
{team.name}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupPreview;
|
||||
474
src/features/tournaments/components/group-stage-view.tsx
Normal file
@@ -0,0 +1,474 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Stack, Text, Card, Group as MantineGroup, Box, SimpleGrid, Tabs, Collapse, ActionIcon, Button, Alert, Badge } from "@mantine/core";
|
||||
import { CaretCircleDownIcon, CaretCircleUpIcon } from "@phosphor-icons/react";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { Group, GroupConfig } from "../types";
|
||||
import GroupMatchCard from "./group-match-card";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks/use-server-mutation";
|
||||
import { populateKnockoutBracket } from "@/features/matches/server";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { tournamentKeys } from "../queries";
|
||||
|
||||
interface GroupStageViewProps {
|
||||
groups: Group[];
|
||||
matches: Match[];
|
||||
showControls?: boolean;
|
||||
tournamentId?: string;
|
||||
hasKnockoutBracket?: boolean;
|
||||
isRegional?: boolean;
|
||||
groupConfig?: GroupConfig;
|
||||
}
|
||||
|
||||
interface TeamStanding {
|
||||
teamId: string;
|
||||
teamName: string;
|
||||
team: any;
|
||||
wins: number;
|
||||
losses: number;
|
||||
cupsFor: number;
|
||||
cupsAgainst: number;
|
||||
cupDifference: number;
|
||||
}
|
||||
|
||||
const GroupStageView: React.FC<GroupStageViewProps> = ({
|
||||
groups,
|
||||
matches,
|
||||
showControls,
|
||||
tournamentId,
|
||||
hasKnockoutBracket,
|
||||
isRegional,
|
||||
groupConfig,
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [expandedTeams, setExpandedTeams] = useState<Record<string, boolean>>({});
|
||||
|
||||
const populateKnockoutMutation = useServerMutation({
|
||||
mutationFn: populateKnockoutBracket,
|
||||
successMessage: "Knockout bracket populated successfully!",
|
||||
onSuccess: () => {
|
||||
if (tournamentId) {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.details(tournamentId) });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const allGroupMatchesCompleted = useMemo(() => {
|
||||
const groupMatches = matches.filter((match) => match.round === -1);
|
||||
if (groupMatches.length === 0) return false;
|
||||
return groupMatches.every((match) => match.status === "ended");
|
||||
}, [matches]);
|
||||
|
||||
const handlePopulateKnockout = () => {
|
||||
if (!tournamentId) return;
|
||||
populateKnockoutMutation.mutate({ data: { tournamentId } });
|
||||
};
|
||||
|
||||
const orderMatchesWithSpacing = (matches: Match[]): Match[] => {
|
||||
if (matches.length <= 1) return matches;
|
||||
|
||||
const ordered: Match[] = [];
|
||||
const remaining = [...matches];
|
||||
|
||||
ordered.push(remaining.shift()!);
|
||||
|
||||
while (remaining.length > 0) {
|
||||
const lastMatch = ordered[ordered.length - 1];
|
||||
const lastTeams = new Set([lastMatch.home?.id, lastMatch.away?.id].filter(Boolean));
|
||||
|
||||
let bestMatchIndex = remaining.findIndex((match) => {
|
||||
const currentTeams = new Set([match.home?.id, match.away?.id].filter(Boolean));
|
||||
for (const teamId of currentTeams) {
|
||||
if (lastTeams.has(teamId)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (bestMatchIndex === -1) {
|
||||
bestMatchIndex = 0;
|
||||
}
|
||||
|
||||
ordered.push(remaining.splice(bestMatchIndex, 1)[0]);
|
||||
}
|
||||
|
||||
return ordered;
|
||||
};
|
||||
|
||||
const matchesByGroup = useMemo(() => {
|
||||
const map = new Map<string, Match[]>();
|
||||
|
||||
matches
|
||||
.filter((match) => match.round === -1 && match.group)
|
||||
.forEach((match) => {
|
||||
if (!map.has(match.group!)) {
|
||||
map.set(match.group!, []);
|
||||
}
|
||||
map.get(match.group!)!.push(match);
|
||||
});
|
||||
|
||||
map.forEach((groupMatches, groupId) => {
|
||||
map.set(groupId, orderMatchesWithSpacing(groupMatches));
|
||||
});
|
||||
|
||||
return map;
|
||||
}, [matches]);
|
||||
|
||||
const sortedGroups = useMemo(() => {
|
||||
return [...groups].sort((a, b) => a.order - b.order);
|
||||
}, [groups]);
|
||||
|
||||
const toggleTeams = (groupId: string) => {
|
||||
setExpandedTeams((prev) => ({
|
||||
...prev,
|
||||
[groupId]: !prev[groupId],
|
||||
}));
|
||||
};
|
||||
|
||||
const getTeamStandings = (groupId: string, teams: any[]): TeamStanding[] => {
|
||||
const groupMatches = matchesByGroup.get(groupId) || [];
|
||||
const standings: Map<string, TeamStanding> = new Map();
|
||||
|
||||
teams.forEach((team) => {
|
||||
standings.set(team.id, {
|
||||
teamId: team.id,
|
||||
teamName: team.name,
|
||||
team: team,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
cupsFor: 0,
|
||||
cupsAgainst: 0,
|
||||
cupDifference: 0,
|
||||
});
|
||||
});
|
||||
|
||||
const completedMatches = groupMatches.filter((match) => match.status === "ended");
|
||||
|
||||
completedMatches.forEach((match) => {
|
||||
const homeId = match.home?.id;
|
||||
const awayId = match.away?.id;
|
||||
|
||||
if (!homeId || !awayId) return;
|
||||
|
||||
const homeStanding = standings.get(homeId);
|
||||
const awayStanding = standings.get(awayId);
|
||||
|
||||
if (!homeStanding || !awayStanding) return;
|
||||
|
||||
const homeCups = match.home_cups || 0;
|
||||
const awayCups = match.away_cups || 0;
|
||||
|
||||
homeStanding.cupsFor += homeCups;
|
||||
homeStanding.cupsAgainst += awayCups;
|
||||
awayStanding.cupsFor += awayCups;
|
||||
awayStanding.cupsAgainst += homeCups;
|
||||
|
||||
homeStanding.cupDifference += homeCups - awayCups;
|
||||
awayStanding.cupDifference += awayCups - homeCups;
|
||||
|
||||
if (homeCups > awayCups) {
|
||||
homeStanding.wins++;
|
||||
awayStanding.losses++;
|
||||
} else if (awayCups > homeCups) {
|
||||
awayStanding.wins++;
|
||||
homeStanding.losses++;
|
||||
}
|
||||
});
|
||||
|
||||
const h2hRecords = new Map<string, Map<string, { wins: number; cupDiff: number }>>();
|
||||
|
||||
completedMatches.forEach((match) => {
|
||||
const homeId = match.home?.id;
|
||||
const awayId = match.away?.id;
|
||||
|
||||
if (!homeId || !awayId) return;
|
||||
|
||||
if (!h2hRecords.has(homeId)) {
|
||||
h2hRecords.set(homeId, new Map());
|
||||
}
|
||||
if (!h2hRecords.has(awayId)) {
|
||||
h2hRecords.set(awayId, new Map());
|
||||
}
|
||||
|
||||
const homeH2H = h2hRecords.get(homeId)!;
|
||||
const awayH2H = h2hRecords.get(awayId)!;
|
||||
|
||||
if (!homeH2H.has(awayId)) {
|
||||
homeH2H.set(awayId, { wins: 0, cupDiff: 0 });
|
||||
}
|
||||
if (!awayH2H.has(homeId)) {
|
||||
awayH2H.set(homeId, { wins: 0, cupDiff: 0 });
|
||||
}
|
||||
|
||||
const homeRecord = homeH2H.get(awayId)!;
|
||||
const awayRecord = awayH2H.get(homeId)!;
|
||||
|
||||
const homeCups = match.home_cups || 0;
|
||||
const awayCups = match.away_cups || 0;
|
||||
|
||||
const cupDiff = homeCups - awayCups;
|
||||
homeRecord.cupDiff += cupDiff;
|
||||
awayRecord.cupDiff -= cupDiff;
|
||||
|
||||
if (homeCups > awayCups) {
|
||||
homeRecord.wins++;
|
||||
} else if (awayCups > homeCups) {
|
||||
awayRecord.wins++;
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(standings.values()).sort((a, b) => {
|
||||
if (b.wins !== a.wins) return b.wins - a.wins;
|
||||
|
||||
if (b.cupDifference !== a.cupDifference) return b.cupDifference - a.cupDifference;
|
||||
|
||||
if (b.cupsFor !== a.cupsFor) return b.cupsFor - a.cupsFor;
|
||||
|
||||
const aH2H = h2hRecords.get(a.teamId);
|
||||
const bH2H = h2hRecords.get(b.teamId);
|
||||
|
||||
if (aH2H && bH2H) {
|
||||
const aVsB = aH2H.get(b.teamId);
|
||||
const bVsA = bH2H.get(a.teamId);
|
||||
|
||||
if (aVsB && bVsA) {
|
||||
if (aVsB.wins !== bVsA.wins) return bVsA.wins - aVsB.wins;
|
||||
|
||||
if (aVsB.cupDiff !== -bVsA.cupDiff) return aVsB.cupDiff - (-bVsA.cupDiff);
|
||||
}
|
||||
}
|
||||
|
||||
return a.teamId.localeCompare(b.teamId);
|
||||
});
|
||||
};
|
||||
|
||||
const allGroupStandings = useMemo(() => {
|
||||
return sortedGroups.map((group) => ({
|
||||
groupId: group.id,
|
||||
groupOrder: group.order,
|
||||
standings: getTeamStandings(group.id, group.teams || []),
|
||||
}));
|
||||
}, [sortedGroups, matchesByGroup]);
|
||||
|
||||
const advancingTeams = useMemo(() => {
|
||||
if (!groupConfig) return { qualifiedTeams: new Set<string>(), wildcardTeams: new Set<string>() };
|
||||
|
||||
const advancePerGroup = groupConfig.advance_per_group;
|
||||
const qualifiedTeams = new Set<string>();
|
||||
const wildcardTeams = new Set<string>();
|
||||
|
||||
allGroupStandings.forEach(({ standings }) => {
|
||||
standings.slice(0, advancePerGroup).forEach((standing) => {
|
||||
qualifiedTeams.add(standing.teamId);
|
||||
});
|
||||
});
|
||||
|
||||
const totalQualified = qualifiedTeams.size;
|
||||
const knockoutTeamCount = totalQualified;
|
||||
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(knockoutTeamCount)));
|
||||
const wildcardsNeeded = nextPowerOf2 - knockoutTeamCount;
|
||||
|
||||
if (wildcardsNeeded > 0) {
|
||||
const allNonQualifiedTeams: TeamStanding[] = [];
|
||||
|
||||
allGroupStandings.forEach(({ standings }) => {
|
||||
standings.slice(advancePerGroup).forEach((standing) => {
|
||||
allNonQualifiedTeams.push(standing);
|
||||
});
|
||||
});
|
||||
|
||||
allNonQualifiedTeams.sort((a, b) => {
|
||||
if (b.wins !== a.wins) return b.wins - a.wins;
|
||||
if (b.cupDifference !== a.cupDifference) return b.cupDifference - a.cupDifference;
|
||||
if (b.cupsFor !== a.cupsFor) return b.cupsFor - a.cupsFor;
|
||||
return a.teamId.localeCompare(b.teamId);
|
||||
});
|
||||
|
||||
allNonQualifiedTeams.slice(0, wildcardsNeeded).forEach((standing) => {
|
||||
wildcardTeams.add(standing.teamId);
|
||||
});
|
||||
}
|
||||
|
||||
return { qualifiedTeams, wildcardTeams };
|
||||
}, [allGroupStandings, groupConfig]);
|
||||
|
||||
if (sortedGroups.length === 0) {
|
||||
return (
|
||||
<Box p="md">
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
No groups have been created yet
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const showGenerateKnockoutButton = showControls && tournamentId && !hasKnockoutBracket && allGroupMatchesCompleted;
|
||||
|
||||
return (
|
||||
<Box p="md">
|
||||
<Stack gap="md">
|
||||
{showGenerateKnockoutButton && (
|
||||
<Alert color="blue" title="Group Stage Complete" icon={<CaretCircleUpIcon size={20} />}>
|
||||
<Stack gap="xs">
|
||||
<Text size="sm">All group matches are finished! Populate the knockout bracket to advance qualified teams.</Text>
|
||||
<Button
|
||||
onClick={handlePopulateKnockout}
|
||||
loading={populateKnockoutMutation.isPending}
|
||||
size="sm"
|
||||
>
|
||||
Populate Knockout Bracket
|
||||
</Button>
|
||||
</Stack>
|
||||
</Alert>
|
||||
)}
|
||||
<Tabs defaultValue={sortedGroups[0]?.id}>
|
||||
<Tabs.List mb="md" grow>
|
||||
{sortedGroups.map((group) => {
|
||||
const groupMatches = matchesByGroup.get(group.id) || [];
|
||||
const completedMatches = groupMatches.filter((m) => m.status === "ended").length;
|
||||
const totalMatches = groupMatches.length;
|
||||
|
||||
return (
|
||||
<Tabs.Tab
|
||||
key={group.id}
|
||||
value={group.id}
|
||||
style={{
|
||||
padding: '12px 20px',
|
||||
}}
|
||||
>
|
||||
<MantineGroup gap="xs" justify="center" wrap="nowrap">
|
||||
<Text fw={600} size="sm">Group {group.name}</Text>
|
||||
<Text size="xs" c="dimmed" style={{ opacity: 0.7 }}>
|
||||
({completedMatches}/{totalMatches})
|
||||
</Text>
|
||||
</MantineGroup>
|
||||
</Tabs.Tab>
|
||||
);
|
||||
})}
|
||||
</Tabs.List>
|
||||
|
||||
{sortedGroups.map((group) => {
|
||||
const groupMatches = matchesByGroup.get(group.id) || [];
|
||||
const standings = getTeamStandings(group.id, group.teams || []);
|
||||
|
||||
return (
|
||||
<Tabs.Panel key={group.id} value={group.id}>
|
||||
<Stack gap="md">
|
||||
<Card withBorder radius="md" p={0}>
|
||||
<MantineGroup
|
||||
justify="space-between"
|
||||
p="sm"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
backgroundColor: 'var(--mantine-color-default-hover)',
|
||||
}}
|
||||
onClick={() => toggleTeams(group.id)}
|
||||
>
|
||||
<Text fw={600} size="sm">
|
||||
Standings ({standings.length})
|
||||
</Text>
|
||||
<ActionIcon variant="subtle" size="sm">
|
||||
{expandedTeams[group.id] ? <CaretCircleUpIcon size={16} /> : <CaretCircleDownIcon size={16} />}
|
||||
</ActionIcon>
|
||||
</MantineGroup>
|
||||
<Collapse in={expandedTeams[group.id]}>
|
||||
<Stack gap={0}>
|
||||
{standings.length > 0 ? (
|
||||
standings.map((standing, index) => {
|
||||
const isQualified = advancingTeams.qualifiedTeams.has(standing.teamId);
|
||||
const isWildcard = advancingTeams.wildcardTeams.has(standing.teamId);
|
||||
const isAdvancing = isQualified || isWildcard;
|
||||
|
||||
return (
|
||||
<MantineGroup
|
||||
key={standing.teamId}
|
||||
gap="sm"
|
||||
align="center"
|
||||
wrap="nowrap"
|
||||
px="md"
|
||||
py="xs"
|
||||
style={{
|
||||
borderTop: index > 0 ? '1px solid var(--mantine-color-default-border)' : 'none',
|
||||
backgroundColor: isAdvancing ? 'var(--mantine-primary-color-light)' : undefined,
|
||||
borderLeft: isAdvancing ? '3px solid var(--mantine-primary-color-filled)' : '3px solid transparent',
|
||||
}}
|
||||
>
|
||||
<Text size="sm" fw={700} c="dimmed" w={24} ta="center">
|
||||
{index + 1}
|
||||
</Text>
|
||||
<TeamAvatar team={standing.team} size={28} radius="sm" isRegional={isRegional} />
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={500} lineClamp={1}>
|
||||
{standing.teamName}
|
||||
</Text>
|
||||
</Box>
|
||||
<MantineGroup gap="xs" wrap="nowrap">
|
||||
{isWildcard && (
|
||||
<Badge size="xs" color="yellow" variant="light">
|
||||
WC
|
||||
</Badge>
|
||||
)}
|
||||
{isQualified && (
|
||||
<Badge size="xs" variant="light">
|
||||
Q
|
||||
</Badge>
|
||||
)}
|
||||
<Text size="xs" c="dimmed" fw={500} miw={35} ta="center">
|
||||
{standing.wins}-{standing.losses}
|
||||
</Text>
|
||||
<Text
|
||||
size="xs"
|
||||
c={standing.cupDifference > 0 ? "green" : standing.cupDifference < 0 ? "red" : "dimmed"}
|
||||
fw={600}
|
||||
miw={30}
|
||||
ta="center"
|
||||
>
|
||||
{standing.cupDifference > 0 ? '+' : ''}{standing.cupDifference}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" fw={400} miw={40} ta="center">
|
||||
{standing.cupsFor}/{standing.cupsAgainst}
|
||||
</Text>
|
||||
</MantineGroup>
|
||||
</MantineGroup>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Text size="sm" c="dimmed" ta="center" py="md">
|
||||
No teams assigned
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Collapse>
|
||||
</Card>
|
||||
|
||||
{groupMatches.length === 0 ? (
|
||||
<Card withBorder radius="md" p="xl">
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
No matches scheduled
|
||||
</Text>
|
||||
</Card>
|
||||
) : (
|
||||
<SimpleGrid
|
||||
cols={{ base: 1, sm: 2, lg: 3 }}
|
||||
spacing="md"
|
||||
>
|
||||
{groupMatches.map((match) => (
|
||||
<GroupMatchCard
|
||||
key={match.id}
|
||||
match={match}
|
||||
showControls={showControls}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
</Stack>
|
||||
</Tabs.Panel>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupStageView;
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
TreeStructureIcon,
|
||||
UsersThreeIcon,
|
||||
UsersIcon,
|
||||
ShuffleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import EditEnrolledTeams from "./edit-enrolled-teams";
|
||||
import EditEnrolledPlayers from "./edit-enrolled-players";
|
||||
import ListLink from "@/components/list-link";
|
||||
import { RichTextEditor } from "@/components/rich-text-editor";
|
||||
import React from "react";
|
||||
import EditRules from "./edit-rules";
|
||||
|
||||
@@ -61,11 +62,20 @@ const ManageTournament = ({ tournamentId }: ManageTournamentProps) => {
|
||||
Icon={UsersThreeIcon}
|
||||
onClick={openEditTeams}
|
||||
/>
|
||||
<ListLink
|
||||
label="Manage Team Songs/Logos"
|
||||
Icon={UsersIcon}
|
||||
to={`/admin/tournaments/${tournamentId}/teams`}
|
||||
/>
|
||||
{tournament.regional && (
|
||||
<ListLink
|
||||
label="Assign Partners"
|
||||
Icon={ShuffleIcon}
|
||||
to={`/admin/tournaments/${tournamentId}/assign-partners`}
|
||||
/>
|
||||
)}
|
||||
{!tournament.regional && (
|
||||
<ListLink
|
||||
label="Manage Team Songs/Logos"
|
||||
Icon={UsersIcon}
|
||||
to={`/admin/tournaments/${tournamentId}/teams`}
|
||||
/>
|
||||
)}
|
||||
<ListLink
|
||||
label="Run Tournament"
|
||||
Icon={TreeStructureIcon}
|
||||
@@ -102,11 +112,15 @@ const ManageTournament = ({ tournamentId }: ManageTournamentProps) => {
|
||||
</Sheet>
|
||||
|
||||
<Sheet
|
||||
title="Edit Enrolled Teams"
|
||||
title={tournament.regional === true ? "Manage Enrollments" : "Edit Enrolled Teams"}
|
||||
opened={editTeamsOpened}
|
||||
onChange={closeEditTeams}
|
||||
>
|
||||
<EditEnrolledTeams tournamentId={tournamentId} />
|
||||
{tournament.regional === true ? (
|
||||
<EditEnrolledPlayers tournamentId={tournamentId} />
|
||||
) : (
|
||||
<EditEnrolledTeams tournamentId={tournamentId} />
|
||||
)}
|
||||
</Sheet>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ const Profile = ({ id }: ProfileProps) => {
|
||||
{
|
||||
label: "Teams",
|
||||
content: <>
|
||||
<TeamList teams={tournament.teams || []} />
|
||||
<TeamList teams={tournament.teams || []} isRegional={tournament.regional} />
|
||||
</>
|
||||
}
|
||||
], [tournament]);
|
||||
|
||||
305
src/features/tournaments/components/setup-group-stage.tsx
Normal file
@@ -0,0 +1,305 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import {
|
||||
Stack,
|
||||
Text,
|
||||
Group,
|
||||
Button,
|
||||
Select,
|
||||
LoadingOverlay,
|
||||
Alert,
|
||||
Title,
|
||||
Divider,
|
||||
Box,
|
||||
} from "@mantine/core";
|
||||
import { InfoIcon } from "@phosphor-icons/react";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks/use-server-mutation";
|
||||
import { generateGroupStage } from "../server";
|
||||
import { TeamInfo } from "@/features/teams/types";
|
||||
import {
|
||||
calculateGroupConfigurations,
|
||||
assignTeamsToGroups,
|
||||
getGroupName,
|
||||
GroupConfigOption,
|
||||
} from "../utils/group-config";
|
||||
import GroupPreview from "./group-preview";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { tournamentKeys } from "../queries";
|
||||
import brackets from "@/features/bracket/utils";
|
||||
import { Bracket } from "@/features/bracket/components/bracket";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { generateSingleEliminationBracket } from "../utils/bracket-generator";
|
||||
|
||||
interface SetupGroupStageProps {
|
||||
tournamentId: string;
|
||||
teams: TeamInfo[];
|
||||
}
|
||||
|
||||
const SetupGroupStage: React.FC<SetupGroupStageProps> = ({
|
||||
tournamentId,
|
||||
teams,
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [selectedConfigIndex, setSelectedConfigIndex] = useState<string>("0");
|
||||
const [seed, setSeed] = useState<number>(Date.now());
|
||||
|
||||
const configurations = useMemo(() => {
|
||||
try {
|
||||
return calculateGroupConfigurations(teams.length);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}, [teams.length]);
|
||||
|
||||
const selectedConfig: GroupConfigOption | null = useMemo(() => {
|
||||
const index = parseInt(selectedConfigIndex);
|
||||
return configurations[index] || null;
|
||||
}, [selectedConfigIndex, configurations]);
|
||||
|
||||
const groupAssignments = useMemo(() => {
|
||||
if (!selectedConfig) return [];
|
||||
|
||||
const teamIds = teams.map((t) => t.id);
|
||||
const assignments = assignTeamsToGroups(teamIds, selectedConfig, seed);
|
||||
|
||||
return assignments.map((teamIds, index) => ({
|
||||
groupIndex: index,
|
||||
groupName: getGroupName(index),
|
||||
teams: teamIds.map((id) => teams.find((t) => t.id === id)!).filter(Boolean),
|
||||
teamIds,
|
||||
}));
|
||||
}, [selectedConfig, teams, seed]);
|
||||
|
||||
const knockoutTeamCount = useMemo(() => {
|
||||
if (!selectedConfig) return 0;
|
||||
return selectedConfig.num_groups * selectedConfig.advance_per_group;
|
||||
}, [selectedConfig]);
|
||||
|
||||
const bracketPreview = useMemo(() => {
|
||||
if (!knockoutTeamCount || !selectedConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(knockoutTeamCount)));
|
||||
const bracketSize = nextPowerOf2;
|
||||
const wildcardsNeeded = bracketSize - knockoutTeamCount;
|
||||
|
||||
let bracketTemplate: any;
|
||||
if (Object.keys(brackets).includes(bracketSize.toString())) {
|
||||
bracketTemplate = brackets[bracketSize as keyof typeof brackets];
|
||||
} else {
|
||||
try {
|
||||
bracketTemplate = generateSingleEliminationBracket(bracketSize);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const groupNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
||||
const seedLabels: Record<number, string> = {};
|
||||
|
||||
const totalTeams = selectedConfig.num_groups * selectedConfig.advance_per_group;
|
||||
let seedIndex = 1;
|
||||
|
||||
for (let i = 0; i < totalTeams / 2; i++) {
|
||||
const group1 = i % selectedConfig.num_groups;
|
||||
const rankIndex1 = Math.floor(i / selectedConfig.num_groups);
|
||||
const rank1 = rankIndex1 + 1;
|
||||
const groupName1 = groupNames[group1] || `Group ${group1 + 1}`;
|
||||
const rankSuffix1 = rank1 === 1 ? '1st' : rank1 === 2 ? '2nd' : rank1 === 3 ? '3rd' : `${rank1}th`;
|
||||
seedLabels[seedIndex++] = `${groupName1} ${rankSuffix1}`;
|
||||
|
||||
const group2 = (i + 1) % selectedConfig.num_groups;
|
||||
const rankIndex2 = selectedConfig.advance_per_group - 1 - rankIndex1;
|
||||
const rank2 = rankIndex2 + 1;
|
||||
const groupName2 = groupNames[group2] || `Group ${group2 + 1}`;
|
||||
const rankSuffix2 = rank2 === 1 ? '1st' : rank2 === 2 ? '2nd' : rank2 === 3 ? '3rd' : `${rank2}th`;
|
||||
seedLabels[seedIndex++] = `${groupName2} ${rankSuffix2}`;
|
||||
}
|
||||
|
||||
for (let i = 0; i < wildcardsNeeded; i++) {
|
||||
seedLabels[seedIndex++] = `Wildcard ${i + 1}`;
|
||||
}
|
||||
|
||||
const ordersMap: Record<number, number> = {};
|
||||
bracketTemplate.winners.forEach((round: any[]) => {
|
||||
round.forEach((match: any) => {
|
||||
ordersMap[match.lid] = match.order;
|
||||
});
|
||||
});
|
||||
|
||||
const placeholderMatches: Match[][] = bracketTemplate.winners.map((round: any[], roundIndex: number) =>
|
||||
round.map((match: any) => {
|
||||
const matchData: any = {
|
||||
...match,
|
||||
id: `preview-${match.lid}`,
|
||||
home_from_lid: match.home_from_lid !== null && match.home_from_lid !== undefined ? match.home_from_lid : -1,
|
||||
away_from_lid: match.away_from_lid !== null && match.away_from_lid !== undefined ? match.away_from_lid : -1,
|
||||
home_cups: 0,
|
||||
away_cups: 0,
|
||||
status: "tbd" as const,
|
||||
tournament: { id: "", name: "" },
|
||||
};
|
||||
|
||||
if (roundIndex === 0) {
|
||||
matchData.home = match.home_seed && !match.bye ? { id: `seed-${match.home_seed}`, name: seedLabels[match.home_seed] } : null;
|
||||
matchData.away = match.away_seed ? { id: `seed-${match.away_seed}`, name: seedLabels[match.away_seed] } : null;
|
||||
} else {
|
||||
matchData.home = null;
|
||||
matchData.away = null;
|
||||
}
|
||||
|
||||
return matchData;
|
||||
})
|
||||
);
|
||||
|
||||
return { matches: placeholderMatches, orders: ordersMap };
|
||||
}, [knockoutTeamCount, selectedConfig]);
|
||||
|
||||
const generateGroups = useServerMutation({
|
||||
mutationFn: generateGroupStage,
|
||||
successMessage: "Group stage generated successfully!",
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: tournamentKeys.details(tournamentId),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleGenerateGroups = () => {
|
||||
if (!selectedConfig) return;
|
||||
|
||||
generateGroups.mutate({
|
||||
data: {
|
||||
tournamentId,
|
||||
groupConfig: {
|
||||
num_groups: selectedConfig.num_groups,
|
||||
teams_per_group: selectedConfig.teams_per_group,
|
||||
advance_per_group: selectedConfig.advance_per_group,
|
||||
matches_guaranteed: selectedConfig.matches_guaranteed,
|
||||
seeding_method: selectedConfig.seeding_method,
|
||||
},
|
||||
teamAssignments: groupAssignments.map((g) => ({
|
||||
groupIndex: g.groupIndex,
|
||||
groupName: g.groupName,
|
||||
teamIds: g.teamIds,
|
||||
})),
|
||||
seed,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleShuffle = () => {
|
||||
setSeed(Date.now());
|
||||
};
|
||||
|
||||
if (configurations.length === 0) {
|
||||
return (
|
||||
<Alert color="red" title="Cannot create groups" icon={<InfoIcon />}>
|
||||
Need at least 4 teams to create a group stage format. Current team count: {teams.length}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "2rem", alignItems: "flex-start" }}>
|
||||
<Stack gap="lg" style={{ flexShrink: 0, width: 400 }}>
|
||||
<Stack gap={0} pos="relative">
|
||||
<LoadingOverlay visible={generateGroups.isPending} />
|
||||
|
||||
<Group gap="xs" p="md" pb="sm" align="center">
|
||||
<Text fw={600} size="lg">
|
||||
Group Stage Setup
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" ml="auto">
|
||||
{teams.length} teams
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Stack gap="md" p="md" pt={0}>
|
||||
<Stack gap="xs">
|
||||
<Text size="sm" fw={500}>
|
||||
Group Configuration
|
||||
</Text>
|
||||
<Select
|
||||
value={selectedConfigIndex}
|
||||
onChange={(value) => setSelectedConfigIndex(value || "0")}
|
||||
data={configurations.map((config, index) => ({
|
||||
value: index.toString(),
|
||||
label: config.description,
|
||||
}))}
|
||||
styles={{
|
||||
dropdown: { maxHeight: 300, overflowY: "auto" },
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{selectedConfig && (
|
||||
<Stack gap="xs">
|
||||
<Text size="xs" c="dimmed">
|
||||
{selectedConfig.total_group_matches} total group stage matches
|
||||
</Text>
|
||||
{selectedConfig.wildcards_needed > 0 && (
|
||||
<Text size="xs" c="yellow">
|
||||
⚠ {selectedConfig.wildcards_needed} wildcard spot{selectedConfig.wildcards_needed > 1 ? 's' : ''} needed for knockout bracket
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Button size="sm" variant="light" onClick={handleShuffle}>
|
||||
Shuffle Groups
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleGenerateGroups}
|
||||
loading={generateGroups.isPending}
|
||||
disabled={!selectedConfig}
|
||||
>
|
||||
Confirm Setup
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<div style={{ flex: 1, overflow: "auto", maxHeight: "80vh" }}>
|
||||
<Stack gap="xl">
|
||||
{groupAssignments.length > 0 && (
|
||||
<GroupPreview groups={groupAssignments} />
|
||||
)}
|
||||
|
||||
{bracketPreview && knockoutTeamCount > 0 && (
|
||||
<Box>
|
||||
<Divider mb="lg" />
|
||||
<Title order={3} ta="center" mb="md">
|
||||
Knockout Bracket Preview ({selectedConfig?.knockout_size} Teams)
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" ta="center" mb="lg">
|
||||
Top {selectedConfig?.advance_per_group} team{selectedConfig?.advance_per_group !== 1 ? 's' : ''} from each group will advance
|
||||
{selectedConfig?.wildcards_needed ? ` + ${selectedConfig.wildcards_needed} wildcard${selectedConfig.wildcards_needed > 1 ? 's' : ''}` : ''}
|
||||
</Text>
|
||||
<Box
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(circle, var(--mantine-color-default-border) 1px, transparent 1px)`,
|
||||
backgroundSize: "16px 16px",
|
||||
backgroundPosition: "0 0, 8px 8px",
|
||||
padding: "1rem",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<Bracket
|
||||
rounds={bracketPreview.matches}
|
||||
orders={bracketPreview.orders}
|
||||
showControls={false}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetupGroupStage;
|
||||
@@ -4,7 +4,7 @@ import { useAuth } from "@/contexts/auth-context";
|
||||
import { Box, Divider, Stack, Text, Card, Center } from "@mantine/core";
|
||||
import { Carousel } from "@mantine/carousel";
|
||||
import ListLink from "@/components/list-link";
|
||||
import { TreeStructureIcon, UsersIcon, ClockIcon, TrophyIcon } from "@phosphor-icons/react";
|
||||
import { TreeStructureIcon, UsersIcon, ClockIcon, ListDashes } from "@phosphor-icons/react";
|
||||
import TeamListButton from "../upcoming-tournament/team-list-button";
|
||||
import RulesListButton from "../upcoming-tournament/rules-list-button";
|
||||
import MatchCard from "@/features/matches/components/match-card";
|
||||
@@ -37,6 +37,10 @@ const StartedTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
return finalsMatch?.status === 'ended';
|
||||
}, [tournament.matches]);
|
||||
|
||||
const hasGroupStage = useMemo(() => {
|
||||
return tournament.matches?.some((match) => match.round === -1) || false;
|
||||
}, [tournament.matches]);
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Header tournament={tournament} />
|
||||
@@ -83,12 +87,19 @@ const StartedTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
Icon={UsersIcon}
|
||||
/>
|
||||
)}
|
||||
{hasGroupStage && (
|
||||
<ListLink
|
||||
label={`View Groups`}
|
||||
to={`/tournaments/${tournament.id}/groups`}
|
||||
Icon={ListDashes}
|
||||
/>
|
||||
)}
|
||||
<ListLink
|
||||
label={`View Bracket`}
|
||||
to={`/tournaments/${tournament.id}/bracket`}
|
||||
Icon={TreeStructureIcon}
|
||||
/>
|
||||
<TeamListButton teams={tournament.teams || []} />
|
||||
<TeamListButton teams={tournament.teams || []} isRegional={tournament.regional} />
|
||||
<RulesListButton tournamentId={tournament.id} />
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Card, Group, Stack, Text, Avatar } from "@mantine/core";
|
||||
import { PlayerInfo } from "@/features/players/types";
|
||||
import PlayerAvatar from "@/components/player-avatar";
|
||||
|
||||
interface TeamAssignment {
|
||||
player1: PlayerInfo;
|
||||
player2: PlayerInfo;
|
||||
teamName: string;
|
||||
}
|
||||
|
||||
interface TeamAssignmentPreviewProps {
|
||||
assignments: TeamAssignment[];
|
||||
}
|
||||
|
||||
const TeamAssignmentPreview: React.FC<TeamAssignmentPreviewProps> = ({ assignments }) => {
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
{assignments.map((assignment, index) => (
|
||||
<Card key={index} withBorder radius="md" p="md">
|
||||
<Group gap="md" align="center" wrap="nowrap">
|
||||
<Text size="sm" fw={600} c="dimmed" w={40}>
|
||||
#{index + 1}
|
||||
</Text>
|
||||
|
||||
<Group gap="sm" style={{ flex: 1 }} align="center">
|
||||
<PlayerAvatar name={`${assignment.player1.first_name} ${assignment.player1.last_name}`} size={32} />
|
||||
<Text size="sm" fw={500}>
|
||||
{assignment.player1.first_name} {assignment.player1.last_name}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Text size="lg" c="dimmed">
|
||||
&
|
||||
</Text>
|
||||
|
||||
<Group gap="sm" style={{ flex: 1 }} align="center">
|
||||
<PlayerAvatar name={`${assignment.player2.first_name} ${assignment.player2.last_name}`} size={32} />
|
||||
<Text size="sm" fw={500}>
|
||||
{assignment.player2.first_name} {assignment.player2.last_name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamAssignmentPreview;
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
Alert,
|
||||
} from "@mantine/core";
|
||||
import { Tournament } from "@/features/tournaments/types";
|
||||
import { CrownIcon, TreeStructureIcon, InfoIcon } from "@phosphor-icons/react";
|
||||
import Avatar from "@/components/avatar";
|
||||
import { CrownIcon, TreeStructureIcon, InfoIcon, ListDashes } from "@phosphor-icons/react";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
import ListLink from "@/components/list-link";
|
||||
import { Podium } from "./podium";
|
||||
|
||||
@@ -33,6 +33,10 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
|
||||
[nonByeMatches]
|
||||
);
|
||||
|
||||
const hasGroupStage = useMemo(() => {
|
||||
return tournament.matches?.some((match) => match.round === -1) || false;
|
||||
}, [tournament.matches]);
|
||||
|
||||
const sortedTeamStats = useMemo(() => {
|
||||
return [...(tournament.team_stats || [])].sort((a, b) => {
|
||||
if (b.wins !== a.wins) {
|
||||
@@ -68,6 +72,8 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
|
||||
<Text px="md" size="lg" fw={600}>Results</Text>
|
||||
<Text px="md" c="dimmed" size="xs" fw={500}>Sorted by win percentage</Text>
|
||||
{teamStatsWithCalculations.map((stat, index) => {
|
||||
const team = tournament.teams?.find(t => t.id === stat.team_id);
|
||||
|
||||
return (
|
||||
<Box key={stat.id}>
|
||||
<UnstyledButton
|
||||
@@ -77,7 +83,11 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
|
||||
>
|
||||
<Group justify="space-between" align="center" w="100%">
|
||||
<Group gap="sm" align="center">
|
||||
<Avatar name={stat.team_name} size={40} radius="sm" />
|
||||
{team ? (
|
||||
<TeamAvatar team={team} size={40} radius="sm" isRegional={tournament.regional} />
|
||||
) : (
|
||||
<TeamAvatar team={{ id: stat.team_id, name: stat.team_name, players: [] } as any} size={40} radius="sm" isRegional={tournament.regional} />
|
||||
)}
|
||||
<Stack gap={2}>
|
||||
<Group gap='xs'>
|
||||
<Text size="xs" c="dimmed">
|
||||
@@ -157,12 +167,19 @@ export const TournamentStats = memo(({ tournament }: TournamentStatsProps) => {
|
||||
return (
|
||||
<Container size="100%" px={0}>
|
||||
<Stack gap="md">
|
||||
{tournament.regional && (
|
||||
{tournament.regional && !hasGroupStage && (
|
||||
<Alert px="md" variant="light" title="Regional Tournament" icon={<InfoIcon size={16} />}>
|
||||
Regional tournaments are a work in progress. Some features might not work as expected.
|
||||
Earlier regional formats aren't supported in the app and order of matches or displayed winners may be unreliable.
|
||||
</Alert>
|
||||
)}
|
||||
{!tournament.regional && <Podium tournament={tournament} />}
|
||||
{hasGroupStage && (
|
||||
<ListLink
|
||||
label={`View Groups`}
|
||||
to={`/tournaments/${tournament.id}/groups`}
|
||||
Icon={ListDashes}
|
||||
/>
|
||||
)}
|
||||
<ListLink
|
||||
label={`View Bracket`}
|
||||
to={`/tournaments/${tournament.id}/bracket`}
|
||||
|
||||
@@ -5,13 +5,12 @@ import { useSheet } from "@/hooks/use-sheet";
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
import useEnrollFreeAgent from "../../hooks/use-enroll-free-agent";
|
||||
|
||||
const EnrollFreeAgent = ({ tournamentId }: {tournamentId: string} ) => {
|
||||
const EnrollFreeAgent = ({ tournamentId, isRegional }: {tournamentId: string, isRegional?: boolean} ) => {
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
const { user, phone } = useAuth();
|
||||
|
||||
const { mutate: enrollFreeAgent, isPending: isEnrolling } = useEnrollFreeAgent();
|
||||
const { mutate: enrollFreeAgent, isPending: isEnrolling } = useEnrollFreeAgent(isRegional);
|
||||
const handleEnroll = () => {
|
||||
console.log('enrolling...')
|
||||
enrollFreeAgent({ playerId: user!.id, tournamentId, phone }, {
|
||||
onSuccess: () => {
|
||||
toggle();
|
||||
@@ -22,21 +21,31 @@ const EnrollFreeAgent = ({ tournamentId }: {tournamentId: string} ) => {
|
||||
return (
|
||||
<>
|
||||
<Button variant="subtle" size="sm" onClick={open}>
|
||||
Enroll As Free Agent
|
||||
{isRegional ? "Enroll" : "Enroll As Free Agent"}
|
||||
</Button>
|
||||
|
||||
<Sheet title="Free Agent Enrollment" opened={isOpen} onChange={toggle}>
|
||||
<Sheet title={isRegional ? "Enrollment" : "Free Agent Enrollment"} opened={isOpen} onChange={toggle}>
|
||||
<Stack gap="xs">
|
||||
<Text size="md">
|
||||
Enrolling as a free agent adds you to a pool of players looking for teammates.
|
||||
</Text>
|
||||
<Text size="sm" c='dimmed'>
|
||||
Once enrolled, you can view other free agents and their phone number in order to coordinate teams and walkout songs.
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Important: Enrolling as a free agent does not guarantee a tournament spot. To secure a spot, one team member must register through the app and select a walkout song.
|
||||
</Text>
|
||||
<Button onClick={handleEnroll}>Confirm</Button>
|
||||
{isRegional ? (
|
||||
<>
|
||||
<Text size="md">
|
||||
Enroll in this regional tournament to be assigned a random partner.
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text size="md">
|
||||
Enrolling as a free agent adds you to a pool of players looking for teammates.
|
||||
</Text>
|
||||
<Text size="sm" c='dimmed'>
|
||||
Once enrolled, you can view other free agents and their phone number in order to coordinate teams and walkout songs.
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Important: Enrolling as a free agent does not guarantee a tournament spot. To secure a spot, one team member must register through the app and select a walkout song.
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Button onClick={handleEnroll} loading={isEnrolling}>Confirm</Button>
|
||||
<Button variant="subtle" color="red" onClick={toggle}>Cancel</Button>
|
||||
</Stack>
|
||||
</Sheet>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Group, Stack, Text, Card, Badge, Box, ActionIcon } from "@mantine/core";
|
||||
import { UserIcon, PhoneIcon } from "@phosphor-icons/react";
|
||||
import { PhoneIcon, CheckCircleIcon } from "@phosphor-icons/react";
|
||||
import { useFreeAgents } from "../../queries";
|
||||
import UnenrollFreeAgent from "./unenroll-free-agent";
|
||||
import toast from "@/lib/sonner";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import PlayerAvatar from "@/components/player-avatar";
|
||||
|
||||
const EnrolledFreeAgent: React.FC<{ tournamentId: string }> = ({
|
||||
tournamentId
|
||||
const EnrolledFreeAgent: React.FC<{ tournamentId: string, isRegional?: boolean }> = ({
|
||||
tournamentId,
|
||||
isRegional
|
||||
}) => {
|
||||
const { data: freeAgents } = useFreeAgents(tournamentId);
|
||||
const { user } = useAuth();
|
||||
|
||||
const copyToClipboard = async (phone: string) => {
|
||||
try {
|
||||
@@ -38,33 +42,66 @@ const EnrolledFreeAgent: React.FC<{ tournamentId: string }> = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (isRegional) {
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Card withBorder radius="md" p="md">
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Group gap="md" align="center">
|
||||
<PlayerAvatar name={`${user?.first_name} ${user?.last_name}`} size={48} />
|
||||
<Box>
|
||||
<Text size="sm" fw={600}>
|
||||
{user?.first_name} {user?.last_name}
|
||||
</Text>
|
||||
<Group gap={4} align="center">
|
||||
<CheckCircleIcon size={14} weight="fill" color="var(--mantine-color-green-6)" />
|
||||
<Text size="xs" c="green" fw={500}>
|
||||
Enrolled
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
|
||||
<Text size="xs" c="dimmed" ta="center">
|
||||
Partners will be randomly assigned when enrollment closes
|
||||
</Text>
|
||||
|
||||
<UnenrollFreeAgent tournamentId={tournamentId} isRegional={isRegional} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between" align="center">
|
||||
<Group gap="xs" align="center">
|
||||
<UserIcon size={16} />
|
||||
<Text size="sm" fw={500}>
|
||||
Enrolled as Free Agent
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600} c="green">
|
||||
✓ Enrolled as Free Agent
|
||||
</Text>
|
||||
|
||||
<Text size="xs" c="dimmed">
|
||||
You're on the free agent list. Other free agents looking for teams:
|
||||
Other players looking for teammates:
|
||||
</Text>
|
||||
|
||||
{freeAgents.length > 1 ? (
|
||||
<Card withBorder radius="md" p="sm">
|
||||
<Stack gap="xs">
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="xs" fw={500} c="dimmed">
|
||||
Free Agents
|
||||
</Text>
|
||||
<Badge variant="light" size="xs" color="blue">
|
||||
{freeAgents.length}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Stack gap="xs">
|
||||
{freeAgents
|
||||
.filter(agent => agent.player)
|
||||
.map((agent) => (
|
||||
<Group key={agent.id} justify="space-between" align="center" wrap="nowrap">
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={500} truncate>
|
||||
{agent.player?.first_name} {agent.player?.last_name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Group key={agent.id} justify="space-between" align="center" wrap="nowrap" p="xs" style={{ borderRadius: '8px', backgroundColor: 'var(--mantine-color-gray-0)' }}>
|
||||
<Text size="sm" fw={500} truncate>
|
||||
{agent.player?.first_name} {agent.player?.last_name}
|
||||
</Text>
|
||||
{agent.phone && (
|
||||
<Group gap={4} align="center" style={{ flexShrink: 0 }}>
|
||||
<ActionIcon
|
||||
@@ -87,27 +124,15 @@ const EnrolledFreeAgent: React.FC<{ tournamentId: string }> = ({
|
||||
)}
|
||||
</Group>
|
||||
))}
|
||||
|
||||
{freeAgents.length > 1 && (
|
||||
<Badge
|
||||
variant="light"
|
||||
size="xs"
|
||||
color="blue"
|
||||
style={{ alignSelf: 'flex-start', marginTop: '4px' }}
|
||||
>
|
||||
{freeAgents.length} free agents total
|
||||
</Badge>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
) : (
|
||||
<Card withBorder radius="md" p="sm">
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
You're the only free agent so far
|
||||
</Text>
|
||||
</Card>
|
||||
<Text size="xs" c="dimmed" py="sm">
|
||||
You're the first free agent!
|
||||
</Text>
|
||||
)}
|
||||
<UnenrollFreeAgent tournamentId={tournamentId} />
|
||||
|
||||
<UnenrollFreeAgent tournamentId={tournamentId} isRegional={isRegional} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import ListButton from "@/components/list-button";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import { UserListIcon } from "@phosphor-icons/react";
|
||||
import { useMemo } from "react";
|
||||
import { useFreeAgents } from "../../queries";
|
||||
import { Text } from "@mantine/core";
|
||||
import PlayerList from "@/features/players/components/player-list";
|
||||
import { Player } from "@/features/players/types";
|
||||
|
||||
interface EnrolledPlayersListButtonProps {
|
||||
tournamentId: string;
|
||||
}
|
||||
|
||||
const EnrolledPlayersListButton: React.FC<EnrolledPlayersListButtonProps> = ({ tournamentId }) => {
|
||||
const { data: freeAgents } = useFreeAgents(tournamentId);
|
||||
const count = useMemo(() => freeAgents.length, [freeAgents]);
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
|
||||
const players = useMemo(() =>
|
||||
freeAgents.map(agent => agent.player).filter((player): player is Player => player !== undefined),
|
||||
[freeAgents]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListButton
|
||||
label={`View Enrolled Players (${count})`}
|
||||
Icon={UserListIcon}
|
||||
onClick={open}
|
||||
/>
|
||||
|
||||
<Sheet title="Enrolled Players" opened={isOpen} onChange={toggle}>
|
||||
{count === 0 ? (
|
||||
<Text size="sm" c="dimmed" ta="center" py="xl">
|
||||
No players enrolled yet
|
||||
</Text>
|
||||
) : (
|
||||
<PlayerList players={players} />
|
||||
)}
|
||||
</Sheet>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrolledPlayersListButton;
|
||||
@@ -37,7 +37,7 @@ const Header = ({ tournament }: { tournament: Tournament }) => {
|
||||
>
|
||||
<TrophyIcon size={32} />
|
||||
</GlitchAvatar>
|
||||
<Flex gap="xs" direction="column" justify="space-around">
|
||||
<Flex gap="xs" direction="column" justify="space-around" align="center">
|
||||
{tournament.location && (
|
||||
<Group gap="xs">
|
||||
<ThemeIcon size="sm" variant="light" radius="sm">
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Suspense, useCallback, useMemo } from "react";
|
||||
import { Suspense, useMemo } from "react";
|
||||
import { Tournament } from "../../types";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { Box, Button, Card, Divider, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { Box, Card, Divider, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import Countdown from "@/components/countdown";
|
||||
import ListLink from "@/components/list-link";
|
||||
import ListButton from "@/components/list-button";
|
||||
import { TreeStructureIcon, UsersIcon } from "@phosphor-icons/react";
|
||||
import EnrollTeam from "./enroll-team";
|
||||
import EnrollFreeAgent from "./enroll-free-agent";
|
||||
import TeamListButton from "./team-list-button";
|
||||
import EnrolledPlayersListButton from "./enrolled-players-list-button";
|
||||
import Header from "./header";
|
||||
import TeamCardSkeleton from "@/features/teams/components/team-card-skeleton";
|
||||
import TeamCard from "@/features/teams/components/team-card";
|
||||
import RegionalTeamCard from "@/features/teams/components/regional-team-card";
|
||||
import UpdateTeam from "./update-team";
|
||||
import UnenrollTeam from "./unenroll-team";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
@@ -52,7 +53,7 @@ const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
<Header tournament={tournament} />
|
||||
|
||||
<Stack px="xs">
|
||||
{tournament.desc && <Text px="md" size="sm">{tournament.desc}</Text>}
|
||||
{tournament.desc && <Text px="md" ta="center" size="sm" style={{ whiteSpace: 'pre-wrap' }}>{tournament.desc}</Text>}
|
||||
|
||||
<Card withBorder radius="lg" p="lg">
|
||||
<Stack gap="xs">
|
||||
@@ -80,34 +81,52 @@ const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
|
||||
{!isUserEnrolled && isEnrollmentOpen && !isFreeAgent && (
|
||||
<>
|
||||
<EnrollTeam
|
||||
{!tournament.regional && (
|
||||
<>
|
||||
<EnrollTeam
|
||||
tournamentId={tournament.id}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
<Divider my={0} label="or" />
|
||||
</>
|
||||
)}
|
||||
<EnrollFreeAgent
|
||||
tournamentId={tournament.id}
|
||||
onSubmit={handleSubmit}
|
||||
isRegional={tournament.regional}
|
||||
/>
|
||||
<Divider my={0} label="or" />
|
||||
<EnrollFreeAgent tournamentId={tournament.id} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isUserEnrolled && (
|
||||
<>
|
||||
<Suspense fallback={<TeamCardSkeleton />}>
|
||||
<TeamCard teamId={userTeam.id} />
|
||||
{tournament.regional === true ? (
|
||||
<RegionalTeamCard teamId={userTeam.id} />
|
||||
) : (
|
||||
<TeamCard teamId={userTeam.id} />
|
||||
)}
|
||||
</Suspense>
|
||||
<UpdateTeam tournamentId={tournament.id} teamId={userTeam.id} />
|
||||
{isEnrollmentOpen && (
|
||||
<UnenrollTeam
|
||||
tournamentId={tournament.id}
|
||||
teamId={userTeam.id}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
{tournament.regional !== true && (
|
||||
<>
|
||||
<UpdateTeam tournamentId={tournament.id} teamId={userTeam.id} />
|
||||
{isEnrollmentOpen && (
|
||||
<UnenrollTeam
|
||||
tournamentId={tournament.id}
|
||||
teamId={userTeam.id}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{
|
||||
isFreeAgent && isEnrollmentOpen && (
|
||||
<EnrolledFreeAgent tournamentId={tournament.id} />
|
||||
<EnrolledFreeAgent
|
||||
tournamentId={tournament.id}
|
||||
isRegional={tournament.regional}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -130,7 +149,15 @@ const UpcomingTournament: React.FC<{ tournament: Tournament }> = ({
|
||||
Icon={TreeStructureIcon}
|
||||
disabled
|
||||
/>
|
||||
<TeamListButton teams={tournament.teams || []} />
|
||||
{tournament.regional === true ? (
|
||||
(tournament.teams && tournament.teams.length > 0) ? (
|
||||
<TeamListButton teams={tournament.teams} isRegional={true} />
|
||||
) : (
|
||||
<EnrolledPlayersListButton tournamentId={tournament.id} />
|
||||
)
|
||||
) : (
|
||||
<TeamListButton teams={tournament.teams || []} isRegional={false} />
|
||||
)}
|
||||
<RulesListButton tournamentId={tournament.id} />
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -8,9 +8,10 @@ import { useMemo } from "react"
|
||||
|
||||
interface TeamListButtonProps {
|
||||
teams: TeamInfo[]
|
||||
isRegional?: boolean
|
||||
}
|
||||
|
||||
const TeamListButton: React.FC<TeamListButtonProps> = ({ teams }) => {
|
||||
const TeamListButton: React.FC<TeamListButtonProps> = ({ teams, isRegional }) => {
|
||||
const count = useMemo(() => teams.length, [teams]);
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
return (
|
||||
@@ -22,7 +23,7 @@ const TeamListButton: React.FC<TeamListButtonProps> = ({ teams }) => {
|
||||
/>
|
||||
|
||||
<Sheet title="Enrolled Teams" opened={isOpen} onChange={toggle}>
|
||||
<TeamList teams={teams} />
|
||||
<TeamList teams={teams} isRegional={isRegional} />
|
||||
</Sheet>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useSheet } from "@/hooks/use-sheet";
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
import useUnenrollFreeAgent from "../../hooks/use-unenroll-free-agent";
|
||||
|
||||
const UnenrollFreeAgent = ({ tournamentId }: {tournamentId: string} ) => {
|
||||
const UnenrollFreeAgent = ({ tournamentId, isRegional }: {tournamentId: string, isRegional?: boolean} ) => {
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
const { user } = useAuth();
|
||||
|
||||
const { mutate: unenrollFreeAgent, isPending: isEnrolling } = useUnenrollFreeAgent();
|
||||
const { mutate: unenrollFreeAgent, isPending: isEnrolling } = useUnenrollFreeAgent(isRegional);
|
||||
const handleUnenroll = () => {
|
||||
unenrollFreeAgent({ playerId: user!.id, tournamentId }, {
|
||||
onSuccess: () => {
|
||||
@@ -20,17 +20,21 @@ const UnenrollFreeAgent = ({ tournamentId }: {tournamentId: string} ) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="subtle" size="sm" onClick={open}>
|
||||
<Button variant="subtle" size="sm" color="red" onClick={open}>
|
||||
Unenroll
|
||||
</Button>
|
||||
|
||||
<Sheet title="Are you sure?" opened={isOpen} onChange={toggle}>
|
||||
<Sheet title="Unenroll from tournament?" opened={isOpen} onChange={toggle}>
|
||||
<Stack gap="xs">
|
||||
<Text size="md">
|
||||
This will remove you from the free agent list.
|
||||
{isRegional
|
||||
? "This will remove you from the tournament enrollment."
|
||||
: "This will remove you from the free agent list."}
|
||||
</Text>
|
||||
<Button onClick={handleUnenroll}>Confirm</Button>
|
||||
<Button variant="subtle" color="red" onClick={toggle}>Cancel</Button>
|
||||
<Button color="red" onClick={handleUnenroll} loading={isEnrolling}>
|
||||
Confirm Unenrollment
|
||||
</Button>
|
||||
<Button variant="subtle" onClick={toggle}>Cancel</Button>
|
||||
</Stack>
|
||||
</Sheet>
|
||||
</>
|
||||
|
||||
21
src/features/tournaments/hooks/use-admin-enroll-player.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { adminEnrollPlayer } from "@/features/tournaments/server";
|
||||
import { tournamentKeys } from "../queries";
|
||||
|
||||
const useAdminEnrollPlayer = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useServerMutation({
|
||||
mutationFn: (data: { tournamentId: string, playerId: string }) => {
|
||||
return adminEnrollPlayer({ data });
|
||||
},
|
||||
onSuccess: (data, { tournamentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.free_agents(tournamentId) });
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.details(tournamentId) });
|
||||
},
|
||||
successMessage: "Player enrolled successfully",
|
||||
});
|
||||
};
|
||||
|
||||
export default useAdminEnrollPlayer;
|
||||
21
src/features/tournaments/hooks/use-admin-unenroll-player.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { adminUnenrollPlayer } from "@/features/tournaments/server";
|
||||
import { tournamentKeys } from "../queries";
|
||||
|
||||
const useAdminUnenrollPlayer = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useServerMutation({
|
||||
mutationFn: (data: { tournamentId: string, playerId: string }) => {
|
||||
return adminUnenrollPlayer({ data });
|
||||
},
|
||||
onSuccess: (data, { tournamentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.free_agents(tournamentId) });
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.details(tournamentId) });
|
||||
},
|
||||
successMessage: "Player removed successfully",
|
||||
});
|
||||
};
|
||||
|
||||
export default useAdminUnenrollPlayer;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks/use-server-mutation";
|
||||
import { confirmTeamAssignments } from "../server";
|
||||
|
||||
export default function useConfirmTeamAssignments() {
|
||||
return useServerMutation({
|
||||
mutationFn: confirmTeamAssignments,
|
||||
successMessage: "Teams created successfully!",
|
||||
});
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { enrollFreeAgent } from "@/features/tournaments/server";
|
||||
import { tournamentKeys } from "../queries";
|
||||
|
||||
const useEnrollFreeAgent = () => {
|
||||
const useEnrollFreeAgent = (isRegional?: boolean) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useServerMutation({
|
||||
@@ -13,7 +13,7 @@ const useEnrollFreeAgent = () => {
|
||||
onSuccess: (data, { tournamentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.free_agents(tournamentId) });
|
||||
},
|
||||
successMessage: 'You\'ve been added as a free agent!',
|
||||
successMessage: isRegional ? "You've enrolled in regionals!" : "You've been added as a free agent!",
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks/use-server-mutation";
|
||||
import { generateRandomTeams } from "../server";
|
||||
|
||||
export default function useGenerateRandomTeams() {
|
||||
return useServerMutation({
|
||||
mutationFn: generateRandomTeams,
|
||||
});
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { unenrollFreeAgent } from "@/features/tournaments/server";
|
||||
import { tournamentKeys } from "../queries";
|
||||
|
||||
const useUnenrollFreeAgent = () => {
|
||||
const useUnenrollFreeAgent = (isRegional?: boolean) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useServerMutation({
|
||||
@@ -13,7 +13,7 @@ const useUnenrollFreeAgent = () => {
|
||||
onSuccess: (data, { tournamentId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: tournamentKeys.free_agents(tournamentId) });
|
||||
},
|
||||
successMessage: 'You\'ve been removed as a free agent.',
|
||||
successMessage: isRegional ? "You've been removed from regionals!" : "You've been removed as a free agent.",
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { superTokensAdminFunctionMiddleware, superTokensFunctionMiddleware } from "@/utils/supertokens";
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
import { pbAdmin } from "@/lib/pocketbase/client";
|
||||
import { tournamentInputSchema } from "@/features/tournaments/types";
|
||||
import { tournamentInputSchema, GroupStanding } from "@/features/tournaments/types";
|
||||
import { logger } from ".";
|
||||
import { z } from "zod";
|
||||
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
|
||||
import { serverFnLoggingMiddleware } from "@/utils/activities";
|
||||
import { fa } from "zod/v4/locales";
|
||||
import brackets from "@/features/bracket/utils";
|
||||
import { MatchInput } from "@/features/matches/types";
|
||||
import { generateSingleEliminationBracket } from "./utils/bracket-generator";
|
||||
|
||||
export const listTournaments = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
@@ -129,3 +131,761 @@ export const unenrollFreeAgent = createServerFn()
|
||||
logger.info('Player unenrolled as free agent', { playerId: player.id });
|
||||
})
|
||||
);
|
||||
|
||||
export const generateRandomTeams = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
tournamentId: z.string(),
|
||||
seed: z.number().optional()
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
const freeAgents = await pbAdmin.getFreeAgents(data.tournamentId);
|
||||
|
||||
if (freeAgents.length < 2) {
|
||||
throw new Error("Need at least 2 players to create teams");
|
||||
}
|
||||
|
||||
if (freeAgents.length % 2 !== 0) {
|
||||
throw new Error("Need an even number of players to create teams");
|
||||
}
|
||||
|
||||
const uniqueFreeAgents = Array.from(
|
||||
new Map(
|
||||
freeAgents
|
||||
.filter(fa => fa.player?.id)
|
||||
.map(fa => [fa.player!.id, fa])
|
||||
).values()
|
||||
);
|
||||
|
||||
if (uniqueFreeAgents.length !== freeAgents.length) {
|
||||
logger.warn('Duplicate free agents detected', {
|
||||
freeAgentCount: freeAgents.length,
|
||||
uniquePlayerCount: uniqueFreeAgents.length
|
||||
});
|
||||
}
|
||||
|
||||
if (uniqueFreeAgents.length < 2) {
|
||||
throw new Error("Need at least 2 unique players to create teams");
|
||||
}
|
||||
|
||||
if (uniqueFreeAgents.length % 2 !== 0) {
|
||||
throw new Error("Need an even number of unique players to create teams");
|
||||
}
|
||||
|
||||
const playerIds = uniqueFreeAgents.map(fa => fa.player!.id);
|
||||
|
||||
const allTeams = await pbAdmin.getTeamsWithFilter(
|
||||
playerIds.map(id => `players.id ?= "${id}"`).join(" || "),
|
||||
"players,tournaments"
|
||||
);
|
||||
|
||||
const invalidPairings = new Set<string>();
|
||||
const mostRecentRegionalPartners = new Map<string, string>();
|
||||
|
||||
let mostRecentRegionalDate: Date | null = null;
|
||||
|
||||
for (const team of allTeams) {
|
||||
const teamPlayers = (team.expand?.players || []) as any[];
|
||||
if (teamPlayers.length !== 2) continue;
|
||||
|
||||
const [p1, p2] = teamPlayers.map((p: any) => p.id).sort();
|
||||
const pairKey = `${p1}|${p2}`;
|
||||
|
||||
const teamTournaments = (team.expand?.tournaments || []) as any[];
|
||||
const hasMainlineTournament = teamTournaments.some((t: any) => !t.regional);
|
||||
|
||||
if (hasMainlineTournament) {
|
||||
invalidPairings.add(pairKey);
|
||||
} else if (team.private && teamTournaments.length > 0) {
|
||||
const regionalTournaments = teamTournaments.filter((t: any) => t.regional);
|
||||
for (const tournament of regionalTournaments) {
|
||||
const tournamentDate = new Date(tournament.start_time);
|
||||
if (!mostRecentRegionalDate || tournamentDate > mostRecentRegionalDate) {
|
||||
mostRecentRegionalDate = tournamentDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mostRecentRegionalDate) {
|
||||
for (const team of allTeams) {
|
||||
if (!team.private) continue;
|
||||
|
||||
const teamPlayers = (team.expand?.players || []) as any[];
|
||||
if (teamPlayers.length !== 2) continue;
|
||||
|
||||
const teamTournaments = (team.expand?.tournaments || []) as any[];
|
||||
const regionalTournaments = teamTournaments.filter((t: any) => t.regional);
|
||||
|
||||
for (const tournament of regionalTournaments) {
|
||||
const tournamentDate = new Date(tournament.start_time);
|
||||
if (tournamentDate.getTime() === mostRecentRegionalDate.getTime()) {
|
||||
const [p1Id, p2Id] = teamPlayers.map((p: any) => p.id);
|
||||
mostRecentRegionalPartners.set(p1Id, p2Id);
|
||||
mostRecentRegionalPartners.set(p2Id, p1Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function canPairPlayers(p1Id: string, p2Id: string): boolean {
|
||||
const pairKey = [p1Id, p2Id].sort().join('|');
|
||||
if (invalidPairings.has(pairKey)) return false;
|
||||
|
||||
const p1LastPartner = mostRecentRegionalPartners.get(p1Id);
|
||||
if (p1LastPartner === p2Id) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const seed = data.seed || Math.floor(Math.random() * 1000000);
|
||||
|
||||
function seededRandom(s: number) {
|
||||
const x = Math.sin(s++) * 10000;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
let currentSeed = seed;
|
||||
const shuffled = [...uniqueFreeAgents];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(seededRandom(currentSeed++) * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
|
||||
const assignments = [];
|
||||
const paired = new Set<string>();
|
||||
const MAX_ATTEMPTS = 1000;
|
||||
let attempts = 0;
|
||||
|
||||
while (paired.size < shuffled.length && attempts < MAX_ATTEMPTS) {
|
||||
attempts++;
|
||||
|
||||
for (let i = 0; i < shuffled.length; i++) {
|
||||
if (paired.has(shuffled[i].player!.id)) continue;
|
||||
|
||||
for (let j = i + 1; j < shuffled.length; j++) {
|
||||
if (paired.has(shuffled[j].player!.id)) continue;
|
||||
|
||||
const player1 = shuffled[i].player!;
|
||||
const player2 = shuffled[j].player!;
|
||||
|
||||
if (canPairPlayers(player1.id, player2.id)) {
|
||||
const teamName = `${player1.first_name} And ${player2.first_name}`;
|
||||
|
||||
assignments.push({
|
||||
player1,
|
||||
player2,
|
||||
teamName
|
||||
});
|
||||
|
||||
paired.add(player1.id);
|
||||
paired.add(player2.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paired.size < shuffled.length) {
|
||||
currentSeed++;
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(seededRandom(currentSeed++) * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
assignments.length = 0;
|
||||
paired.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (paired.size < shuffled.length) {
|
||||
throw new Error("Unable to create valid pairings with current restrictions. Please manually adjust enrollments.");
|
||||
}
|
||||
|
||||
logger.info('Generated random team assignments with restrictions', {
|
||||
tournamentId: data.tournamentId,
|
||||
teamCount: assignments.length,
|
||||
seed,
|
||||
attempts
|
||||
});
|
||||
|
||||
return { assignments, seed };
|
||||
})
|
||||
);
|
||||
|
||||
export const confirmTeamAssignments = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
tournamentId: z.string(),
|
||||
assignments: z.array(z.object({
|
||||
player1Id: z.string(),
|
||||
player2Id: z.string(),
|
||||
teamName: z.string()
|
||||
}))
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
const createdTeams = [];
|
||||
let reusedCount = 0;
|
||||
|
||||
for (const assignment of data.assignments) {
|
||||
const existingTeams = await pbAdmin.getTeamsWithFilter(
|
||||
`private = true && players.id ?= "${assignment.player1Id}" && players.id ?= "${assignment.player2Id}"`,
|
||||
"players,tournaments"
|
||||
);
|
||||
|
||||
let teamToUse = null;
|
||||
|
||||
for (const team of existingTeams) {
|
||||
const teamPlayers = (team.expand?.players || []) as any[];
|
||||
|
||||
if (teamPlayers.length !== 2) continue;
|
||||
|
||||
const playerIds = teamPlayers.map((p: any) => p.id).sort();
|
||||
const assignmentIds = [assignment.player1Id, assignment.player2Id].sort();
|
||||
if (playerIds[0] !== assignmentIds[0] || playerIds[1] !== assignmentIds[1]) continue;
|
||||
|
||||
const teamTournaments = (team.expand?.tournaments || []) as any[];
|
||||
const hasMainlineTournament = teamTournaments.some((t: any) => !t.regional);
|
||||
|
||||
if (!hasMainlineTournament) {
|
||||
teamToUse = team;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (teamToUse) {
|
||||
await pbAdmin.enrollTeam(data.tournamentId, teamToUse.id);
|
||||
createdTeams.push(teamToUse);
|
||||
reusedCount++;
|
||||
logger.info('Reusing existing regional team', { teamId: teamToUse.id, teamName: teamToUse.name });
|
||||
} else {
|
||||
const team = await pbAdmin.createTeam({
|
||||
name: assignment.teamName,
|
||||
players: [assignment.player1Id, assignment.player2Id],
|
||||
private: true
|
||||
});
|
||||
|
||||
await pbAdmin.enrollTeam(data.tournamentId, team.id);
|
||||
createdTeams.push(team);
|
||||
}
|
||||
}
|
||||
|
||||
for (const assignment of data.assignments) {
|
||||
await pbAdmin.unenrollFreeAgent(assignment.player1Id, data.tournamentId);
|
||||
await pbAdmin.unenrollFreeAgent(assignment.player2Id, data.tournamentId);
|
||||
}
|
||||
|
||||
logger.info('Confirmed team assignments', {
|
||||
tournamentId: data.tournamentId,
|
||||
teamCount: createdTeams.length,
|
||||
reusedCount,
|
||||
newCount: createdTeams.length - reusedCount
|
||||
});
|
||||
|
||||
return { teams: createdTeams };
|
||||
})
|
||||
);
|
||||
|
||||
async function calculateGroupStandings(groupId: string): Promise<GroupStanding[]> {
|
||||
const group = await pbAdmin.getGroup(groupId);
|
||||
if (!group) {
|
||||
throw new Error("Group not found");
|
||||
}
|
||||
|
||||
const matches = await pbAdmin.getMatchesByGroup(groupId);
|
||||
const completedMatches = matches.filter(m => m.status === "ended");
|
||||
|
||||
const standings = new Map<string, GroupStanding>();
|
||||
|
||||
for (const team of group.teams || []) {
|
||||
standings.set(team.id, {
|
||||
team,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
cups_for: 0,
|
||||
cups_against: 0,
|
||||
cup_differential: 0,
|
||||
rank: 0,
|
||||
});
|
||||
}
|
||||
|
||||
for (const match of completedMatches) {
|
||||
if (!match.home || !match.away) continue;
|
||||
|
||||
const homeStanding = standings.get(match.home.id);
|
||||
const awayStanding = standings.get(match.away.id);
|
||||
|
||||
if (homeStanding && awayStanding) {
|
||||
homeStanding.cups_for += match.home_cups;
|
||||
homeStanding.cups_against += match.away_cups;
|
||||
awayStanding.cups_for += match.away_cups;
|
||||
awayStanding.cups_against += match.home_cups;
|
||||
|
||||
if (match.home_cups > match.away_cups) {
|
||||
homeStanding.wins++;
|
||||
awayStanding.losses++;
|
||||
} else {
|
||||
awayStanding.wins++;
|
||||
homeStanding.losses++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const standing of standings.values()) {
|
||||
standing.cup_differential = standing.cups_for - standing.cups_against;
|
||||
}
|
||||
|
||||
const h2hRecords = new Map<string, Map<string, { wins: number; cupDiff: number }>>();
|
||||
|
||||
for (const match of completedMatches) {
|
||||
if (!match.home || !match.away) continue;
|
||||
|
||||
if (!h2hRecords.has(match.home.id)) {
|
||||
h2hRecords.set(match.home.id, new Map());
|
||||
}
|
||||
if (!h2hRecords.has(match.away.id)) {
|
||||
h2hRecords.set(match.away.id, new Map());
|
||||
}
|
||||
|
||||
const homeH2H = h2hRecords.get(match.home.id)!;
|
||||
const awayH2H = h2hRecords.get(match.away.id)!;
|
||||
|
||||
if (!homeH2H.has(match.away.id)) {
|
||||
homeH2H.set(match.away.id, { wins: 0, cupDiff: 0 });
|
||||
}
|
||||
if (!awayH2H.has(match.home.id)) {
|
||||
awayH2H.set(match.home.id, { wins: 0, cupDiff: 0 });
|
||||
}
|
||||
|
||||
const homeRecord = homeH2H.get(match.away.id)!;
|
||||
const awayRecord = awayH2H.get(match.home.id)!;
|
||||
|
||||
const cupDiff = match.home_cups - match.away_cups;
|
||||
homeRecord.cupDiff += cupDiff;
|
||||
awayRecord.cupDiff -= cupDiff;
|
||||
|
||||
if (match.home_cups > match.away_cups) {
|
||||
homeRecord.wins++;
|
||||
} else {
|
||||
awayRecord.wins++;
|
||||
}
|
||||
}
|
||||
|
||||
const sortedStandings = Array.from(standings.values()).sort((a, b) => {
|
||||
if (b.wins !== a.wins) return b.wins - a.wins;
|
||||
|
||||
if (b.cup_differential !== a.cup_differential) return b.cup_differential - a.cup_differential;
|
||||
|
||||
if (b.cups_for !== a.cups_for) return b.cups_for - a.cups_for;
|
||||
|
||||
const aH2H = h2hRecords.get(a.team.id);
|
||||
const bH2H = h2hRecords.get(b.team.id);
|
||||
|
||||
if (aH2H && bH2H) {
|
||||
const aVsB = aH2H.get(b.team.id);
|
||||
const bVsA = bH2H.get(a.team.id);
|
||||
|
||||
if (aVsB && bVsA) {
|
||||
if (aVsB.wins !== bVsA.wins) return bVsA.wins - aVsB.wins;
|
||||
|
||||
if (aVsB.cupDiff !== -bVsA.cupDiff) return aVsB.cupDiff - (-bVsA.cupDiff);
|
||||
}
|
||||
}
|
||||
|
||||
return a.team.id.localeCompare(b.team.id);
|
||||
});
|
||||
|
||||
sortedStandings.forEach((standing, index) => {
|
||||
standing.rank = index + 1;
|
||||
});
|
||||
|
||||
return sortedStandings;
|
||||
}
|
||||
|
||||
export const getGroupStandings = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: groupId }) =>
|
||||
toServerResult(() => calculateGroupStandings(groupId))
|
||||
);
|
||||
|
||||
export const generateKnockoutBracket = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
tournamentId: z.string(),
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
logger.info('Generating knockout bracket', {
|
||||
tournamentId: data.tournamentId,
|
||||
});
|
||||
|
||||
const tournament = await pbAdmin.getTournament(data.tournamentId);
|
||||
if (!tournament) {
|
||||
throw new Error("Tournament not found");
|
||||
}
|
||||
|
||||
if (tournament.phase !== "group_stage") {
|
||||
throw new Error("Tournament must be in group_stage phase to generate knockout bracket");
|
||||
}
|
||||
|
||||
if (!tournament.group_config) {
|
||||
throw new Error("Tournament must have group_config");
|
||||
}
|
||||
|
||||
const groups = await pbAdmin.getGroupsByTournament(data.tournamentId);
|
||||
if (!groups || groups.length === 0) {
|
||||
throw new Error("No groups found for tournament");
|
||||
}
|
||||
|
||||
const qualifiedTeams: { teamId: string; groupOrder: number; rank: number }[] = [];
|
||||
const allStandings: { standing: GroupStanding; groupOrder: number }[] = [];
|
||||
|
||||
for (const group of groups) {
|
||||
const standings = await calculateGroupStandings(group.id);
|
||||
|
||||
for (let i = 0; i < standings.length; i++) {
|
||||
allStandings.push({
|
||||
standing: standings[i],
|
||||
groupOrder: group.order,
|
||||
});
|
||||
}
|
||||
|
||||
const topTeams = standings.slice(0, tournament.group_config.advance_per_group);
|
||||
for (const standing of topTeams) {
|
||||
qualifiedTeams.push({
|
||||
teamId: standing.team.id,
|
||||
groupOrder: group.order,
|
||||
rank: standing.rank,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const orderedTeamIds: string[] = [];
|
||||
const maxRank = tournament.group_config.advance_per_group;
|
||||
const numGroups = tournament.group_config.num_groups;
|
||||
|
||||
const teamsByGroup: string[][] = [];
|
||||
for (let g = 0; g < numGroups; g++) {
|
||||
teamsByGroup[g] = [];
|
||||
}
|
||||
|
||||
for (const qualified of qualifiedTeams) {
|
||||
teamsByGroup[qualified.groupOrder][qualified.rank - 1] = qualified.teamId;
|
||||
}
|
||||
|
||||
const totalTeams = numGroups * maxRank;
|
||||
for (let i = 0; i < totalTeams / 2; i++) {
|
||||
const group1 = i % numGroups;
|
||||
const rankIndex1 = Math.floor(i / numGroups);
|
||||
|
||||
const group2 = (i + 1) % numGroups;
|
||||
const rankIndex2 = maxRank - 1 - rankIndex1;
|
||||
|
||||
const team1 = teamsByGroup[group1]?.[rankIndex1];
|
||||
const team2 = teamsByGroup[group2]?.[rankIndex2];
|
||||
|
||||
if (team1) orderedTeamIds.push(team1);
|
||||
if (team2) orderedTeamIds.push(team2);
|
||||
}
|
||||
|
||||
let teamCount = orderedTeamIds.length;
|
||||
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(teamCount)));
|
||||
const wildcardsNeeded = nextPowerOf2 - teamCount;
|
||||
|
||||
if (wildcardsNeeded > 0) {
|
||||
const qualifiedTeamIds = new Set(qualifiedTeams.map(t => t.teamId));
|
||||
const wildcardCandidates = allStandings
|
||||
.filter(s => !qualifiedTeamIds.has(s.standing.team.id))
|
||||
.map(s => s.standing);
|
||||
|
||||
wildcardCandidates.sort((a, b) => {
|
||||
if (b.wins !== a.wins) return b.wins - a.wins;
|
||||
if (b.cup_differential !== a.cup_differential) return b.cup_differential - a.cup_differential;
|
||||
if (b.cups_for !== a.cups_for) return b.cups_for - a.cups_for;
|
||||
return a.team.id.localeCompare(b.team.id);
|
||||
});
|
||||
|
||||
const wildcardTeams = wildcardCandidates.slice(0, wildcardsNeeded);
|
||||
const wildcardTeamIds = wildcardTeams.map(t => t.team.id);
|
||||
|
||||
orderedTeamIds.push(...wildcardTeamIds);
|
||||
teamCount = orderedTeamIds.length;
|
||||
|
||||
logger.info('Added wildcard teams to knockout bracket', {
|
||||
tournamentId: data.tournamentId,
|
||||
wildcardsNeeded,
|
||||
wildcardTeams: wildcardTeams.map(t => ({
|
||||
id: t.team.id,
|
||||
name: t.team.name,
|
||||
wins: t.wins,
|
||||
cupDiff: t.cup_differential,
|
||||
cupsFor: t.cups_for
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
let bracketTemplate: any;
|
||||
if (Object.keys(brackets).includes(teamCount.toString())) {
|
||||
bracketTemplate = brackets[teamCount as keyof typeof brackets];
|
||||
} else {
|
||||
bracketTemplate = generateSingleEliminationBracket(teamCount);
|
||||
}
|
||||
|
||||
const seedToTeamId = new Map<number, string>();
|
||||
orderedTeamIds.forEach((teamId, index) => {
|
||||
seedToTeamId.set(index + 1, teamId);
|
||||
});
|
||||
|
||||
const matchInputs: MatchInput[] = [];
|
||||
let matchLid = 1000;
|
||||
|
||||
bracketTemplate.winners.forEach((round: any[]) => {
|
||||
round.forEach((match: any) => {
|
||||
const matchInput: MatchInput = {
|
||||
lid: matchLid++,
|
||||
round: match.round,
|
||||
order: match.order || 0,
|
||||
reset: false,
|
||||
bye: match.bye || false,
|
||||
home_cups: 0,
|
||||
away_cups: 0,
|
||||
ot_count: 0,
|
||||
home_from_lid: match.home_from_lid === null ? -1 : (match.home_from_lid + 1000),
|
||||
away_from_lid: match.away_from_lid === null ? -1 : (match.away_from_lid + 1000),
|
||||
home_from_loser: false,
|
||||
away_from_loser: false,
|
||||
is_losers_bracket: false,
|
||||
match_type: "knockout",
|
||||
status: "tbd",
|
||||
tournament: data.tournamentId,
|
||||
};
|
||||
|
||||
if (match.home_seed) {
|
||||
const teamId = seedToTeamId.get(match.home_seed);
|
||||
if (teamId) {
|
||||
matchInput.home = teamId;
|
||||
matchInput.home_seed = match.home_seed;
|
||||
}
|
||||
}
|
||||
|
||||
if (match.away_seed) {
|
||||
const teamId = seedToTeamId.get(match.away_seed);
|
||||
if (teamId) {
|
||||
matchInput.away = teamId;
|
||||
matchInput.away_seed = match.away_seed;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchInput.home && matchInput.away) {
|
||||
matchInput.status = "ready";
|
||||
}
|
||||
|
||||
matchInputs.push(matchInput);
|
||||
});
|
||||
});
|
||||
|
||||
const createdMatches = await pbAdmin.createMatches(matchInputs);
|
||||
|
||||
const existingMatchIds = tournament.matches?.map(m => m.id) || [];
|
||||
const newMatchIds = createdMatches.map(m => m.id);
|
||||
await pbAdmin.updateTournamentMatches(data.tournamentId, [...existingMatchIds, ...newMatchIds]);
|
||||
|
||||
await pbAdmin.updateTournament(data.tournamentId, {
|
||||
phase: "knockout"
|
||||
});
|
||||
|
||||
logger.info('Knockout bracket generated', {
|
||||
tournamentId: data.tournamentId,
|
||||
matchCount: createdMatches.length,
|
||||
qualifiedTeamCount: qualifiedTeams.length
|
||||
});
|
||||
|
||||
return {
|
||||
tournament,
|
||||
matchCount: createdMatches.length,
|
||||
matches: createdMatches
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const adminEnrollPlayer = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
playerId: z.string(),
|
||||
tournamentId: z.string()
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
await pbAdmin.enrollFreeAgent(data.playerId, "", data.tournamentId);
|
||||
logger.info('Admin enrolled player', { playerId: data.playerId, tournamentId: data.tournamentId });
|
||||
})
|
||||
);
|
||||
|
||||
export const adminUnenrollPlayer = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
playerId: z.string(),
|
||||
tournamentId: z.string()
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
await pbAdmin.unenrollFreeAgent(data.playerId, data.tournamentId);
|
||||
logger.info('Admin unenrolled player', { playerId: data.playerId, tournamentId: data.tournamentId });
|
||||
})
|
||||
);
|
||||
|
||||
export const generateGroupStage = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
tournamentId: z.string(),
|
||||
groupConfig: z.object({
|
||||
num_groups: z.number(),
|
||||
teams_per_group: z.number(),
|
||||
advance_per_group: z.number(),
|
||||
matches_guaranteed: z.number(),
|
||||
seeding_method: z.enum(["random", "ranked"]),
|
||||
}),
|
||||
teamAssignments: z.array(z.object({
|
||||
groupIndex: z.number(),
|
||||
groupName: z.string(),
|
||||
teamIds: z.array(z.string())
|
||||
})),
|
||||
seed: z.number().optional()
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
logger.info('Generating group stage', {
|
||||
tournamentId: data.tournamentId,
|
||||
numGroups: data.groupConfig.num_groups,
|
||||
seed: data.seed
|
||||
});
|
||||
|
||||
const tournament = await pbAdmin.getTournament(data.tournamentId);
|
||||
if (!tournament) {
|
||||
throw new Error("Tournament not found");
|
||||
}
|
||||
|
||||
if (tournament.matches && tournament.matches.length > 0) {
|
||||
throw new Error("Tournament already has matches generated");
|
||||
}
|
||||
|
||||
await pbAdmin.deleteGroupsByTournament(data.tournamentId);
|
||||
|
||||
const createdGroups = [];
|
||||
const groupStageMatches = [];
|
||||
|
||||
for (const assignment of data.teamAssignments) {
|
||||
const group = await pbAdmin.createGroup({
|
||||
tournament: data.tournamentId,
|
||||
name: assignment.groupName,
|
||||
order: assignment.groupIndex,
|
||||
teams: assignment.teamIds
|
||||
});
|
||||
|
||||
createdGroups.push(group);
|
||||
|
||||
const teamIds = assignment.teamIds;
|
||||
|
||||
for (let i = 0; i < teamIds.length; i++) {
|
||||
for (let j = i + 1; j < teamIds.length; j++) {
|
||||
groupStageMatches.push({
|
||||
lid: -1,
|
||||
round: -1,
|
||||
order: groupStageMatches.length + 1,
|
||||
reset: false,
|
||||
bye: false,
|
||||
home: teamIds[i],
|
||||
away: teamIds[j],
|
||||
home_cups: 0,
|
||||
away_cups: 0,
|
||||
ot_count: 0,
|
||||
home_from_lid: -1,
|
||||
away_from_lid: -1,
|
||||
home_from_loser: false,
|
||||
away_from_loser: false,
|
||||
is_losers_bracket: false,
|
||||
match_type: "group_stage" as const,
|
||||
group: group.id,
|
||||
status: "ready" as const,
|
||||
tournament: data.tournamentId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const knockoutTeamCount = data.groupConfig.num_groups * data.groupConfig.advance_per_group;
|
||||
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(knockoutTeamCount)));
|
||||
const bracketSize = nextPowerOf2;
|
||||
|
||||
let bracketTemplate: any;
|
||||
|
||||
if (Object.keys(brackets).includes(bracketSize.toString())) {
|
||||
bracketTemplate = brackets[bracketSize as keyof typeof brackets];
|
||||
} else {
|
||||
bracketTemplate = generateSingleEliminationBracket(bracketSize);
|
||||
}
|
||||
|
||||
logger.info('Creating knockout bracket template', {
|
||||
tournamentId: data.tournamentId,
|
||||
knockoutTeamCount,
|
||||
bracketSize,
|
||||
wildcardsNeeded: bracketSize - knockoutTeamCount
|
||||
});
|
||||
|
||||
const knockoutMatches: any[] = [];
|
||||
|
||||
bracketTemplate.winners.forEach((round: any[]) => {
|
||||
round.forEach((match: any) => {
|
||||
knockoutMatches.push({
|
||||
lid: match.lid,
|
||||
round: match.round,
|
||||
order: match.order,
|
||||
reset: false,
|
||||
bye: match.bye || false,
|
||||
home_seed: match.home_seed,
|
||||
away_seed: match.away_seed,
|
||||
home_cups: 0,
|
||||
away_cups: 0,
|
||||
ot_count: 0,
|
||||
home_from_lid: match.home_from_lid !== null ? match.home_from_lid : -1,
|
||||
away_from_lid: match.away_from_lid !== null ? match.away_from_lid : -1,
|
||||
home_from_loser: false,
|
||||
away_from_loser: false,
|
||||
is_losers_bracket: false,
|
||||
match_type: "knockout" as const,
|
||||
status: "tbd" as const,
|
||||
tournament: data.tournamentId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const allMatches = [...groupStageMatches, ...knockoutMatches];
|
||||
const createdMatches = await pbAdmin.createMatches(allMatches);
|
||||
|
||||
const matchIds = createdMatches.map((match) => match.id);
|
||||
await pbAdmin.updateTournamentMatches(data.tournamentId, matchIds);
|
||||
|
||||
await pbAdmin.updateTournament(data.tournamentId, {
|
||||
phase: "group_stage",
|
||||
group_config: data.groupConfig
|
||||
});
|
||||
|
||||
logger.info('Group stage and knockout bracket generated', {
|
||||
tournamentId: data.tournamentId,
|
||||
groupCount: createdGroups.length,
|
||||
groupMatchCount: groupStageMatches.length,
|
||||
knockoutMatchCount: knockoutMatches.length,
|
||||
totalMatchCount: createdMatches.length
|
||||
});
|
||||
|
||||
return {
|
||||
tournament,
|
||||
groups: createdGroups,
|
||||
matchCount: createdMatches.length,
|
||||
matches: createdMatches
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
@@ -2,6 +2,37 @@ import { TeamInfo } from "@/features/teams/types";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { z } from "zod";
|
||||
|
||||
export type TournamentFormat = "single_elim" | "double_elim" | "groups" | "swiss" | "swiss_bracket" | "round_robin";
|
||||
export type TournamentPhase = "seeding" | "group_stage" | "knockout" | "completed";
|
||||
|
||||
export interface GroupConfig {
|
||||
num_groups: number;
|
||||
teams_per_group: number;
|
||||
advance_per_group: number;
|
||||
matches_guaranteed: number;
|
||||
seeding_method: "random" | "ranked";
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
id: string;
|
||||
tournament: string;
|
||||
name: string;
|
||||
order: number;
|
||||
teams: TeamInfo[];
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export interface GroupStanding {
|
||||
team: TeamInfo;
|
||||
wins: number;
|
||||
losses: number;
|
||||
cups_for: number;
|
||||
cups_against: number;
|
||||
cup_differential: number;
|
||||
rank: number;
|
||||
}
|
||||
|
||||
export interface TournamentTeamStats {
|
||||
id: string;
|
||||
team_id: string;
|
||||
@@ -52,6 +83,10 @@ export interface Tournament {
|
||||
third_place?: TeamInfo;
|
||||
team_stats?: TournamentTeamStats[];
|
||||
regional?: boolean;
|
||||
format?: TournamentFormat;
|
||||
group_config?: GroupConfig;
|
||||
phase?: TournamentPhase;
|
||||
groups?: Group[];
|
||||
}
|
||||
|
||||
export const tournamentInputSchema = z.object({
|
||||
@@ -64,6 +99,15 @@ export const tournamentInputSchema = z.object({
|
||||
start_time: z.string(),
|
||||
end_time: z.string().optional(),
|
||||
regional: z.boolean().optional().default(false),
|
||||
format: z.enum(["single_elim", "double_elim", "groups", "swiss", "swiss_bracket", "round_robin"]).optional(),
|
||||
phase: z.enum(["seeding", "group_stage", "knockout", "completed"]).optional(),
|
||||
group_config: z.object({
|
||||
num_groups: z.number(),
|
||||
teams_per_group: z.number(),
|
||||
advance_per_group: z.number(),
|
||||
matches_guaranteed: z.number(),
|
||||
seeding_method: z.enum(["random", "ranked"]),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export type TournamentInput = z.infer<typeof tournamentInputSchema>;
|
||||
|
||||
103
src/features/tournaments/utils/bracket-generator.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
export interface BracketMatch {
|
||||
lid: number;
|
||||
round: number;
|
||||
order: number | null;
|
||||
bye: boolean;
|
||||
home_seed?: number;
|
||||
away_seed?: number;
|
||||
home_from_lid: number | null;
|
||||
home_from_loser: boolean;
|
||||
away_from_lid: number | null;
|
||||
away_from_loser: boolean;
|
||||
reset: boolean;
|
||||
}
|
||||
|
||||
export interface BracketTemplate {
|
||||
winners: BracketMatch[][];
|
||||
losers: BracketMatch[][];
|
||||
}
|
||||
|
||||
export function generateSingleEliminationBracket(teamCount: number): BracketTemplate {
|
||||
if (teamCount < 2) {
|
||||
throw new Error("Need at least 2 teams for a bracket");
|
||||
}
|
||||
|
||||
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(teamCount)));
|
||||
const totalRounds = Math.log2(nextPowerOf2);
|
||||
|
||||
const byesNeeded = nextPowerOf2 - teamCount;
|
||||
const firstRoundMatches = Math.floor(teamCount / 2);
|
||||
|
||||
const winners: BracketMatch[][] = [];
|
||||
let currentLid = 0;
|
||||
let currentOrder = 1;
|
||||
|
||||
for (let round = 0; round < totalRounds; round++) {
|
||||
const roundMatches: BracketMatch[] = [];
|
||||
const matchesInRound = Math.pow(2, totalRounds - round - 1);
|
||||
|
||||
for (let matchIndex = 0; matchIndex < matchesInRound; matchIndex++) {
|
||||
const match: BracketMatch = {
|
||||
lid: currentLid++,
|
||||
round,
|
||||
order: currentOrder++,
|
||||
bye: false,
|
||||
home_from_lid: null,
|
||||
home_from_loser: false,
|
||||
away_from_lid: null,
|
||||
away_from_loser: false,
|
||||
reset: false,
|
||||
};
|
||||
|
||||
if (round === 0) {
|
||||
const homePosition = matchIndex * 2;
|
||||
const awayPosition = matchIndex * 2 + 1;
|
||||
|
||||
if (homePosition < teamCount && awayPosition < teamCount) {
|
||||
match.home_seed = homePosition + 1;
|
||||
match.away_seed = awayPosition + 1;
|
||||
} else if (homePosition < teamCount) {
|
||||
match.home_seed = homePosition + 1;
|
||||
match.bye = true;
|
||||
} else {
|
||||
match.bye = true;
|
||||
}
|
||||
} else {
|
||||
const prevRound = winners[round - 1];
|
||||
const homeFeedIndex = matchIndex * 2;
|
||||
const awayFeedIndex = matchIndex * 2 + 1;
|
||||
|
||||
if (homeFeedIndex < prevRound.length) {
|
||||
match.home_from_lid = prevRound[homeFeedIndex].lid;
|
||||
}
|
||||
if (awayFeedIndex < prevRound.length) {
|
||||
match.away_from_lid = prevRound[awayFeedIndex].lid;
|
||||
}
|
||||
}
|
||||
|
||||
roundMatches.push(match);
|
||||
}
|
||||
|
||||
winners.push(roundMatches);
|
||||
}
|
||||
|
||||
return {
|
||||
winners,
|
||||
losers: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function generateGroupMismatchSeeding(
|
||||
numGroups: number,
|
||||
teamsPerGroup: number
|
||||
): number[] {
|
||||
const seeding: number[] = [];
|
||||
|
||||
for (let rank = 0; rank < teamsPerGroup; rank++) {
|
||||
for (let group = 0; group < numGroups; group++) {
|
||||
seeding.push(group * teamsPerGroup + rank);
|
||||
}
|
||||
}
|
||||
|
||||
return seeding;
|
||||
}
|
||||
193
src/features/tournaments/utils/group-config.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { GroupConfig } from "../types";
|
||||
|
||||
function isPowerOfTwo(n: number): boolean {
|
||||
return n > 0 && (n & (n - 1)) === 0;
|
||||
}
|
||||
|
||||
function getNextPowerOfTwo(n: number): number {
|
||||
if (n <= 0) return 1;
|
||||
if (isPowerOfTwo(n)) return n;
|
||||
return Math.pow(2, Math.ceil(Math.log2(n)));
|
||||
}
|
||||
|
||||
export interface GroupConfigOption extends GroupConfig {
|
||||
groups_with_extra: number;
|
||||
knockout_size: number;
|
||||
wildcards_needed: number;
|
||||
total_group_matches: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function calculateGroupConfigurations(teamCount: number): GroupConfigOption[] {
|
||||
if (teamCount < 4) {
|
||||
throw new Error("Need at least 4 teams for group stage");
|
||||
}
|
||||
|
||||
const configs: GroupConfigOption[] = [];
|
||||
|
||||
for (let teamsPerGroup = 3; teamsPerGroup <= Math.min(6, teamCount); teamsPerGroup++) {
|
||||
const numGroupsFloor = Math.floor(teamCount / teamsPerGroup);
|
||||
const numGroupsCeil = Math.ceil(teamCount / teamsPerGroup);
|
||||
|
||||
const groupOptions = new Set([numGroupsFloor, numGroupsCeil]);
|
||||
|
||||
for (const numGroups of groupOptions) {
|
||||
if (numGroups < 2) continue;
|
||||
|
||||
const baseTeamsPerGroup = Math.floor(teamCount / numGroups);
|
||||
const groupsWithExtra = teamCount % numGroups;
|
||||
|
||||
const minGroupSize = baseTeamsPerGroup;
|
||||
const maxGroupSize = baseTeamsPerGroup + (groupsWithExtra > 0 ? 1 : 0);
|
||||
|
||||
if (minGroupSize < 3 || maxGroupSize > 6) continue;
|
||||
|
||||
const matchesGuaranteed = minGroupSize - 1;
|
||||
|
||||
for (let advancePerGroup = 1; advancePerGroup <= Math.min(3, minGroupSize - 1); advancePerGroup++) {
|
||||
const teamsAdvancing = numGroups * advancePerGroup;
|
||||
|
||||
if (teamsAdvancing < 4 || teamsAdvancing > 32) continue;
|
||||
|
||||
const knockoutSize = getNextPowerOfTwo(teamsAdvancing);
|
||||
const wildcardsNeeded = knockoutSize - teamsAdvancing;
|
||||
|
||||
if (wildcardsNeeded > teamsAdvancing / 2) continue;
|
||||
|
||||
let totalGroupMatches = 0;
|
||||
for (let i = 0; i < numGroups; i++) {
|
||||
const groupSize = baseTeamsPerGroup + (i < groupsWithExtra ? 1 : 0);
|
||||
totalGroupMatches += (groupSize * (groupSize - 1)) / 2;
|
||||
}
|
||||
|
||||
const description = generateDescription({
|
||||
num_groups: numGroups,
|
||||
teams_per_group: baseTeamsPerGroup,
|
||||
groups_with_extra: groupsWithExtra,
|
||||
advance_per_group: advancePerGroup,
|
||||
matches_guaranteed: matchesGuaranteed,
|
||||
knockout_size: knockoutSize,
|
||||
wildcards_needed: wildcardsNeeded,
|
||||
});
|
||||
|
||||
configs.push({
|
||||
num_groups: numGroups,
|
||||
teams_per_group: baseTeamsPerGroup,
|
||||
advance_per_group: advancePerGroup,
|
||||
matches_guaranteed: matchesGuaranteed,
|
||||
seeding_method: "random",
|
||||
groups_with_extra: groupsWithExtra,
|
||||
knockout_size: knockoutSize,
|
||||
wildcards_needed: wildcardsNeeded,
|
||||
total_group_matches: totalGroupMatches,
|
||||
description,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueConfigs = new Map<string, GroupConfigOption>();
|
||||
|
||||
for (const config of configs) {
|
||||
const groupSizes: number[] = [];
|
||||
for (let i = 0; i < config.num_groups; i++) {
|
||||
const size = config.teams_per_group + (i < config.groups_with_extra ? 1 : 0);
|
||||
groupSizes.push(size);
|
||||
}
|
||||
groupSizes.sort((a, b) => b - a);
|
||||
|
||||
const key = `${groupSizes.join(',')}_advance${config.advance_per_group}`;
|
||||
|
||||
if (!uniqueConfigs.has(key)) {
|
||||
uniqueConfigs.set(key, config);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(uniqueConfigs.values()).sort((a, b) => {
|
||||
if (a.matches_guaranteed !== b.matches_guaranteed) {
|
||||
return b.matches_guaranteed - a.matches_guaranteed;
|
||||
}
|
||||
if (a.wildcards_needed !== b.wildcards_needed) {
|
||||
return a.wildcards_needed - b.wildcards_needed;
|
||||
}
|
||||
if (a.knockout_size !== b.knockout_size) {
|
||||
return b.knockout_size - a.knockout_size;
|
||||
}
|
||||
return b.num_groups - a.num_groups;
|
||||
});
|
||||
}
|
||||
|
||||
function generateDescription(config: Partial<GroupConfigOption>): string {
|
||||
const { num_groups, teams_per_group, groups_with_extra, matches_guaranteed, advance_per_group, knockout_size, wildcards_needed } = config;
|
||||
|
||||
let desc = '';
|
||||
|
||||
if (groups_with_extra && groups_with_extra > 0 && teams_per_group) {
|
||||
const largerGroupSize = teams_per_group + 1;
|
||||
const smallerGroupCount = num_groups! - groups_with_extra;
|
||||
|
||||
if (smallerGroupCount > 0) {
|
||||
desc += `${groups_with_extra} group${groups_with_extra > 1 ? 's' : ''} of ${largerGroupSize}, `;
|
||||
desc += `${smallerGroupCount} group${smallerGroupCount > 1 ? 's' : ''} of ${teams_per_group}`;
|
||||
} else {
|
||||
desc += `${num_groups} group${num_groups! > 1 ? 's' : ''} of ${largerGroupSize}`;
|
||||
}
|
||||
} else {
|
||||
desc += `${num_groups} group${num_groups! > 1 ? 's' : ''} of ${teams_per_group}`;
|
||||
}
|
||||
|
||||
desc += ` • ${matches_guaranteed} match${matches_guaranteed! > 1 ? 'es' : ''} guaranteed`;
|
||||
desc += ` • Top ${advance_per_group} advance`;
|
||||
|
||||
if (wildcards_needed && wildcards_needed > 0) {
|
||||
desc += ` + ${wildcards_needed} wildcard${wildcards_needed > 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
desc += ` → ${knockout_size}-team knockout`;
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
export function assignTeamsToGroups(
|
||||
teamIds: string[],
|
||||
config: GroupConfigOption,
|
||||
seed?: number
|
||||
): string[][] {
|
||||
const shuffled = shuffleArray([...teamIds], seed);
|
||||
|
||||
const groups: string[][] = [];
|
||||
let teamIndex = 0;
|
||||
|
||||
for (let groupIndex = 0; groupIndex < config.num_groups; groupIndex++) {
|
||||
const groupSize = config.teams_per_group + (groupIndex < config.groups_with_extra ? 1 : 0);
|
||||
const groupTeams = shuffled.slice(teamIndex, teamIndex + groupSize);
|
||||
groups.push(groupTeams);
|
||||
teamIndex += groupSize;
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
function shuffleArray<T>(array: T[], seed?: number): T[] {
|
||||
const arr = [...array];
|
||||
const random = seed !== undefined ? seededRandom(seed) : Math.random;
|
||||
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(random() * (i + 1));
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function seededRandom(seed: number): () => number {
|
||||
let value = seed;
|
||||
return () => {
|
||||
value = (value * 9301 + 49297) % 233280;
|
||||
return value / 233280;
|
||||
};
|
||||
}
|
||||
|
||||
export function getGroupName(index: number): string {
|
||||
return String.fromCharCode(65 + index);
|
||||
}
|
||||
14
src/hooks/use-is-pwa.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useIsPWA(): boolean {
|
||||
const [isPWA, setIsPWA] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
|
||||
const isIOSStandalone = 'standalone' in window.navigator && (window.navigator as any).standalone;
|
||||
|
||||
setIsPWA(isStandalone || isIOSStandalone);
|
||||
}, []);
|
||||
|
||||
return isPWA;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class Logger {
|
||||
constructor(context?: string, options: LoggerOptions = {}) {
|
||||
this.context = context;
|
||||
this.options = {
|
||||
enabled: import.meta.env.NODE_ENV !== "production",
|
||||
enabled: true,
|
||||
showTimestamp: true,
|
||||
collapsed: true,
|
||||
colors: true,
|
||||
@@ -75,27 +75,44 @@ class Logger {
|
||||
|
||||
const groupLabel = `${timestamp}${style.label}${context} │ ${label}`;
|
||||
|
||||
const group = this.options.collapsed
|
||||
? console.groupCollapsed
|
||||
: console.group;
|
||||
// In server environment (no window), use simple console.log instead of groups
|
||||
const isServer = typeof window === "undefined";
|
||||
|
||||
if (this.options.colors && typeof window !== "undefined") {
|
||||
group(`%c${groupLabel}`, `color: ${style.color}; font-weight: bold;`);
|
||||
} else {
|
||||
group(groupLabel);
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
if (rest.length > 0) {
|
||||
for (const item of rest) {
|
||||
console.log(item);
|
||||
if (isServer) {
|
||||
// Server-side: Simple formatted output (no console.group in Node.js)
|
||||
console.log(groupLabel);
|
||||
if (data !== undefined) {
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
}
|
||||
}
|
||||
if (rest.length > 0) {
|
||||
for (const item of rest) {
|
||||
console.log(JSON.stringify(item, null, 2));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Browser: Use console.group with colors
|
||||
const group = this.options.collapsed
|
||||
? console.groupCollapsed
|
||||
: console.group;
|
||||
|
||||
console.groupEnd();
|
||||
if (this.options.colors) {
|
||||
group(`%c${groupLabel}`, `color: ${style.color}; font-weight: bold;`);
|
||||
} else {
|
||||
group(groupLabel);
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
if (rest.length > 0) {
|
||||
for (const item of rest) {
|
||||
console.log(item);
|
||||
}
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
info(label: string, data?: any, ...rest: any[]): void {
|
||||
|
||||
@@ -6,8 +6,7 @@ import { createMatchesService } from "./services/matches";
|
||||
import { createReactionsService } from "./services/reactions";
|
||||
import { createActivitiesService } from "./services/activities";
|
||||
import { createBadgesService } from "./services/badges";
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import { createGroupsService } from "./services/groups";
|
||||
|
||||
class PocketBaseAdminClient {
|
||||
private pb: PocketBase;
|
||||
@@ -48,6 +47,7 @@ class PocketBaseAdminClient {
|
||||
Object.assign(this, createReactionsService(this.pb));
|
||||
Object.assign(this, createActivitiesService(this.pb));
|
||||
Object.assign(this, createBadgesService(this.pb));
|
||||
Object.assign(this, createGroupsService(this.pb));
|
||||
|
||||
this.authPromise = this.authenticate();
|
||||
this.authPromise.then(() => {
|
||||
@@ -125,7 +125,8 @@ interface AdminClient
|
||||
ReturnType<typeof createMatchesService>,
|
||||
ReturnType<typeof createReactionsService>,
|
||||
ReturnType<typeof createActivitiesService>,
|
||||
ReturnType<typeof createBadgesService> {
|
||||
ReturnType<typeof createBadgesService>,
|
||||
ReturnType<typeof createGroupsService> {
|
||||
authPromise: Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,97 @@ export function createBadgesService(pb: PocketBase) {
|
||||
return bigWins.length > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
if (criteria.unique_partners !== undefined) {
|
||||
const matches = await pb.collection("matches").getFullList({
|
||||
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended"`,
|
||||
expand: 'home,away,home.players,away.players',
|
||||
});
|
||||
|
||||
const partners = new Set<string>();
|
||||
|
||||
for (const match of matches) {
|
||||
const isHome = match.expand?.home?.expand?.players?.some((p: any) => p.id === playerId) ||
|
||||
match.expand?.home?.players?.includes(playerId);
|
||||
const isAway = match.expand?.away?.expand?.players?.some((p: any) => p.id === playerId) ||
|
||||
match.expand?.away?.players?.includes(playerId);
|
||||
|
||||
let teamPlayers: any[] = [];
|
||||
if (isHome) {
|
||||
teamPlayers = match.expand?.home?.expand?.players || match.expand?.home?.players || [];
|
||||
} else if (isAway) {
|
||||
teamPlayers = match.expand?.away?.expand?.players || match.expand?.away?.players || [];
|
||||
}
|
||||
|
||||
for (const player of teamPlayers) {
|
||||
const partnerId = typeof player === 'string' ? player : player.id;
|
||||
if (partnerId !== playerId) {
|
||||
partners.add(partnerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return partners.size;
|
||||
}
|
||||
|
||||
if (criteria.beat_mainline_partner !== undefined) {
|
||||
const mainlineMatches = await pb.collection("matches").getFullList({
|
||||
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended" && (tournament.regional = false || tournament.regional = null)`,
|
||||
expand: 'home,away,home.players,away.players',
|
||||
});
|
||||
|
||||
const mainlinePartners = new Set<string>();
|
||||
for (const match of mainlineMatches) {
|
||||
const isHome = match.expand?.home?.expand?.players?.some((p: any) => p.id === playerId) ||
|
||||
match.expand?.home?.players?.includes(playerId);
|
||||
const isAway = match.expand?.away?.expand?.players?.some((p: any) => p.id === playerId) ||
|
||||
match.expand?.away?.players?.includes(playerId);
|
||||
|
||||
let teamPlayers: any[] = [];
|
||||
if (isHome) {
|
||||
teamPlayers = match.expand?.home?.expand?.players || match.expand?.home?.players || [];
|
||||
} else if (isAway) {
|
||||
teamPlayers = match.expand?.away?.expand?.players || match.expand?.away?.players || [];
|
||||
}
|
||||
|
||||
for (const player of teamPlayers) {
|
||||
const partnerId = typeof player === 'string' ? player : player.id;
|
||||
if (partnerId !== playerId) {
|
||||
mainlinePartners.add(partnerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const regionalMatches = await pb.collection("matches").getFullList({
|
||||
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended" && tournament.regional = true`,
|
||||
expand: 'home,away,home.players,away.players',
|
||||
});
|
||||
|
||||
for (const match of regionalMatches) {
|
||||
const isHome = match.expand?.home?.expand?.players?.some((p: any) => p.id === playerId) ||
|
||||
match.expand?.home?.players?.includes(playerId);
|
||||
const isAway = match.expand?.away?.expand?.players?.some((p: any) => p.id === playerId) ||
|
||||
match.expand?.away?.players?.includes(playerId);
|
||||
|
||||
const didWin = (isHome && match.home_cups > match.away_cups) ||
|
||||
(isAway && match.away_cups > match.home_cups);
|
||||
|
||||
if (!didWin) continue;
|
||||
|
||||
const opponentPlayers: any[] = isHome
|
||||
? (match.expand?.away?.expand?.players || match.expand?.away?.players || [])
|
||||
: (match.expand?.home?.expand?.players || match.expand?.home?.players || []);
|
||||
|
||||
for (const opponent of opponentPlayers) {
|
||||
const opponentId = typeof opponent === 'string' ? opponent : opponent.id;
|
||||
if (mainlinePartners.has(opponentId)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
@@ -314,18 +405,34 @@ export function createBadgesService(pb: PocketBase) {
|
||||
}
|
||||
|
||||
if (criteria.tournament_record !== undefined) {
|
||||
const includeRegional = criteria.tournament_record === "0-3";
|
||||
const tournamentFilter = includeRegional
|
||||
? ''
|
||||
: 'regional = false || regional = null';
|
||||
|
||||
const tournaments = await pb.collection("tournaments").getFullList({
|
||||
filter: 'regional = false || regional = null',
|
||||
filter: tournamentFilter,
|
||||
sort: 'start_time',
|
||||
});
|
||||
|
||||
let timesWent02 = 0;
|
||||
const allMatches = includeRegional
|
||||
? await pb.collection("matches").getFullList({
|
||||
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended"`,
|
||||
expand: 'tournament,home,away,home.players,away.players',
|
||||
})
|
||||
: matches;
|
||||
|
||||
for (const tournamentId of tournamentIds) {
|
||||
const relevantTournamentIds = includeRegional
|
||||
? new Set(allMatches.map(m => m.tournament))
|
||||
: tournamentIds;
|
||||
|
||||
let timesMetRecord = 0;
|
||||
|
||||
for (const tournamentId of relevantTournamentIds) {
|
||||
const tournament = tournaments.find(t => t.id === tournamentId);
|
||||
if (!tournament) continue;
|
||||
|
||||
const tournamentMatches = matches.filter(m => m.tournament === tournamentId);
|
||||
const tournamentMatches = allMatches.filter(m => m.tournament === tournamentId);
|
||||
|
||||
let wins = 0;
|
||||
let losses = 0;
|
||||
@@ -353,61 +460,144 @@ export function createBadgesService(pb: PocketBase) {
|
||||
if (currentIndex > 0) {
|
||||
const previousTournament = tournaments[currentIndex - 1];
|
||||
if (previousTournament.winner_id === playerId) {
|
||||
timesWent02++;
|
||||
timesMetRecord++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
timesWent02++;
|
||||
timesMetRecord++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timesWent02 > 0 ? 1 : 0;
|
||||
return timesMetRecord > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
if (criteria.consecutive_wins !== undefined) {
|
||||
const tournaments = await pb.collection("tournaments").getFullList({
|
||||
const allTournaments = await pb.collection("tournaments").getFullList({
|
||||
filter: 'regional = false || regional = null',
|
||||
sort: 'start_time',
|
||||
});
|
||||
|
||||
let consecutiveWins = 0;
|
||||
let maxConsecutiveWins = 0;
|
||||
const tournamentResults: { tournament: any; playerWon: boolean }[] = [];
|
||||
|
||||
for (const tournament of tournaments) {
|
||||
if (!tournamentIds.has(tournament.id)) continue;
|
||||
|
||||
const tournamentMatches = await pb.collection("matches").getFullList({
|
||||
for (const tournament of allTournaments) {
|
||||
const matches = await pb.collection("matches").getFullList({
|
||||
filter: `tournament = "${tournament.id}" && status = "ended"`,
|
||||
expand: 'home,away,home.players,away.players',
|
||||
});
|
||||
|
||||
const winnersMatches = tournamentMatches.filter(m => !m.is_losers_bracket);
|
||||
const finalsMatch = winnersMatches.reduce((highest: any, current: any) =>
|
||||
(!highest || current.lid > highest.lid) ? current : highest, null);
|
||||
if (matches.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (finalsMatch && finalsMatch.status === 'ended') {
|
||||
const finalsWinnerId = (finalsMatch.home_cups > finalsMatch.away_cups) ? finalsMatch.home : finalsMatch.away;
|
||||
const winnersMatches = matches.filter(m => !m.is_losers_bracket);
|
||||
|
||||
const winningTeam = finalsMatch.expand?.[finalsWinnerId === finalsMatch.home ? 'home' : 'away'];
|
||||
const winningPlayers = winningTeam?.expand?.players || winningTeam?.players || [];
|
||||
if (winnersMatches.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const playerWon = winningPlayers.some((p: any) =>
|
||||
(typeof p === 'string' ? p : p.id) === playerId
|
||||
);
|
||||
const finalsMatch = winnersMatches.reduce((highest: any, current: any) => {
|
||||
if (!highest) return current;
|
||||
return (current.lid > highest.lid) ? current : highest;
|
||||
}, null);
|
||||
|
||||
if (playerWon) {
|
||||
consecutiveWins++;
|
||||
maxConsecutiveWins = Math.max(maxConsecutiveWins, consecutiveWins);
|
||||
} else {
|
||||
consecutiveWins = 0;
|
||||
}
|
||||
if (!finalsMatch || finalsMatch.status !== 'ended') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const winningTeamId = (finalsMatch.home_cups > finalsMatch.away_cups)
|
||||
? finalsMatch.home
|
||||
: finalsMatch.away;
|
||||
|
||||
const winningTeam = finalsMatch.expand?.[winningTeamId === finalsMatch.home ? 'home' : 'away'];
|
||||
if (!winningTeam) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const winningPlayers = winningTeam.expand?.players || winningTeam.players || [];
|
||||
|
||||
const playerWon = winningPlayers.some((p: any) => {
|
||||
const pid = (typeof p === 'string') ? p : p.id;
|
||||
return pid === playerId;
|
||||
});
|
||||
|
||||
tournamentResults.push({
|
||||
tournament,
|
||||
playerWon,
|
||||
});
|
||||
}
|
||||
|
||||
let currentStreak = 0;
|
||||
let maxStreak = 0;
|
||||
|
||||
for (const result of tournamentResults) {
|
||||
if (result.playerWon) {
|
||||
currentStreak++;
|
||||
maxStreak = Math.max(maxStreak, currentStreak);
|
||||
} else {
|
||||
consecutiveWins = 0;
|
||||
currentStreak = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return maxConsecutiveWins >= criteria.consecutive_wins ? 1 : 0;
|
||||
return maxStreak;
|
||||
}
|
||||
|
||||
if (criteria.undefeated_group_stage !== undefined) {
|
||||
const regionalTournaments = await pb.collection("tournaments").getFullList({
|
||||
filter: 'regional = true',
|
||||
sort: 'start_time',
|
||||
});
|
||||
|
||||
for (const tournament of regionalTournaments) {
|
||||
const groups = await pb.collection("groups").getFullList({
|
||||
filter: `tournament = "${tournament.id}"`,
|
||||
expand: 'teams,teams.players',
|
||||
});
|
||||
|
||||
for (const group of groups) {
|
||||
const teams = group.expand?.teams || [];
|
||||
let playerTeamId: string | null = null;
|
||||
|
||||
for (const team of teams) {
|
||||
const teamPlayers = team.expand?.players || team.players || [];
|
||||
const isPlayerInTeam = teamPlayers.some((p: any) =>
|
||||
(typeof p === 'string' ? p : p.id) === playerId
|
||||
);
|
||||
if (isPlayerInTeam) {
|
||||
playerTeamId = team.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!playerTeamId) continue;
|
||||
|
||||
const groupMatches = await pb.collection("matches").getFullList({
|
||||
filter: `tournament = "${tournament.id}" && group = "${group.id}" && status = "ended" && (home = "${playerTeamId}" || away = "${playerTeamId}")`,
|
||||
expand: 'home,away',
|
||||
});
|
||||
|
||||
if (groupMatches.length === 0) continue;
|
||||
|
||||
let isUndefeated = true;
|
||||
for (const match of groupMatches) {
|
||||
const isHome = match.home === playerTeamId;
|
||||
const didWin = isHome
|
||||
? match.home_cups > match.away_cups
|
||||
: match.away_cups > match.home_cups;
|
||||
|
||||
if (!didWin) {
|
||||
isUndefeated = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUndefeated) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -427,10 +617,13 @@ export function createBadgesService(pb: PocketBase) {
|
||||
if (criteria.overtime_matches !== undefined) return criteria.overtime_matches;
|
||||
if (criteria.overtime_wins !== undefined) return criteria.overtime_wins;
|
||||
if (criteria.consecutive_wins !== undefined) return criteria.consecutive_wins;
|
||||
if (criteria.unique_partners !== undefined) return criteria.unique_partners;
|
||||
if (criteria.won_tournament !== undefined) return 1;
|
||||
if (criteria.placement !== undefined) return 1;
|
||||
if (criteria.margin_of_victory !== undefined) return 1;
|
||||
if (criteria.tournament_record !== undefined) return 1;
|
||||
if (criteria.beat_mainline_partner !== undefined) return 1;
|
||||
if (criteria.undefeated_group_stage !== undefined) return 1;
|
||||
|
||||
return 1;
|
||||
},
|
||||
|
||||
54
src/lib/pocketbase/services/groups.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { logger } from "@/lib/logger";
|
||||
import PocketBase from "pocketbase";
|
||||
import { Group } from "@/features/tournaments/types";
|
||||
|
||||
export interface GroupInput {
|
||||
tournament: string;
|
||||
name: string;
|
||||
order: number;
|
||||
teams: string[];
|
||||
}
|
||||
|
||||
export function createGroupsService(pb: PocketBase) {
|
||||
return {
|
||||
async createGroup(data: GroupInput): Promise<Group> {
|
||||
logger.info("PocketBase | Creating group", data);
|
||||
const result = await pb.collection("groups").create(data);
|
||||
return result as unknown as Group;
|
||||
},
|
||||
|
||||
async getGroupsByTournament(tournamentId: string): Promise<Group[]> {
|
||||
logger.info("PocketBase | Getting groups for tournament", { tournamentId });
|
||||
const result = await pb.collection("groups").getFullList({
|
||||
filter: `tournament = "${tournamentId}"`,
|
||||
sort: "order",
|
||||
expand: "teams,teams.players"
|
||||
});
|
||||
return result as unknown as Group[];
|
||||
},
|
||||
|
||||
async deleteGroup(groupId: string): Promise<void> {
|
||||
logger.info("PocketBase | Deleting group", { groupId });
|
||||
await pb.collection("groups").delete(groupId);
|
||||
},
|
||||
|
||||
async deleteGroupsByTournament(tournamentId: string): Promise<void> {
|
||||
logger.info("PocketBase | Deleting all groups for tournament", { tournamentId });
|
||||
const groups = await pb.collection("groups").getFullList({
|
||||
filter: `tournament = "${tournamentId}"`
|
||||
});
|
||||
|
||||
for (const group of groups) {
|
||||
await pb.collection("groups").delete(group.id);
|
||||
}
|
||||
},
|
||||
|
||||
async getGroup(groupId: string): Promise<Group> {
|
||||
logger.info("PocketBase | Getting group", { groupId });
|
||||
const result = await pb.collection("groups").getOne(groupId, {
|
||||
expand: "teams,teams.players"
|
||||
});
|
||||
return result as unknown as Group;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -141,5 +141,17 @@ export function createMatchesService(pb: PocketBase) {
|
||||
|
||||
return results.map(match => transformMatch(match));
|
||||
},
|
||||
|
||||
async getMatchesByGroup(groupId: string): Promise<Match[]> {
|
||||
logger.info("PocketBase | Getting matches for group", { groupId });
|
||||
|
||||
const results = await pb.collection("matches").getFullList({
|
||||
filter: `group = "${groupId}"`,
|
||||
expand: "tournament, home, away, home.players, away.players",
|
||||
sort: "created",
|
||||
});
|
||||
|
||||
return results.map(match => transformMatch(match));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,5 +110,14 @@ export function createTeamsService(pb: PocketBase) {
|
||||
|
||||
return result.map((match) => transformMatch(match));
|
||||
},
|
||||
|
||||
async getTeamsWithFilter(filter: string, expand?: string): Promise<any[]> {
|
||||
logger.info("PocketBase | Getting teams with filter", { filter, expand });
|
||||
const result = await pb.collection("teams").getFullList({
|
||||
filter,
|
||||
expand,
|
||||
});
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,17 +14,23 @@ import { PlayerInfo } from "@/features/players/types";
|
||||
export function createTournamentsService(pb: PocketBase) {
|
||||
return {
|
||||
async getTournament(id: string, isAdmin: boolean = false): Promise<Tournament> {
|
||||
const [tournamentResult, teamStatsResult] = await Promise.all([
|
||||
const [tournamentResult, teamStatsResult, groupsResult] = await Promise.all([
|
||||
pb.collection("tournaments").getOne(id, {
|
||||
expand: "teams, teams.players, matches, matches.tournament, matches.home, matches.away, matches.home.players, matches.away.players",
|
||||
}),
|
||||
pb.collection("team_stats_per_tournament").getFullList({
|
||||
filter: `tournament_id = "${id}"`,
|
||||
sort: "-wins,-total_cups_made"
|
||||
}),
|
||||
pb.collection("groups").getFullList({
|
||||
filter: `tournament = "${id}"`,
|
||||
sort: "order",
|
||||
expand: "teams, teams.players"
|
||||
})
|
||||
]);
|
||||
|
||||
tournamentResult.team_stats = teamStatsResult;
|
||||
tournamentResult.groups = groupsResult;
|
||||
|
||||
return transformTournament(tournamentResult, isAdmin);
|
||||
},
|
||||
|
||||
@@ -55,6 +55,8 @@ export const transformMatch = (record: any, isAdmin: boolean = false): Match =>
|
||||
updated: record.updated,
|
||||
home_seed: record.home_seed,
|
||||
away_seed: record.away_seed,
|
||||
match_type: record.match_type,
|
||||
group: record.group,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -247,6 +249,16 @@ export function transformTournament(record: any, isAdmin: boolean = false): Tour
|
||||
}
|
||||
}
|
||||
|
||||
const groups = (record.groups || record.expand?.groups)?.map((group: any) => ({
|
||||
id: group.id,
|
||||
tournament: group.tournament,
|
||||
name: group.name,
|
||||
order: group.order,
|
||||
teams: group.expand?.teams?.map(transformTeamInfo) ?? [],
|
||||
created: group.created,
|
||||
updated: group.updated,
|
||||
})) ?? [];
|
||||
|
||||
return {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
@@ -261,8 +273,12 @@ export function transformTournament(record: any, isAdmin: boolean = false): Tour
|
||||
created: record.created,
|
||||
updated: record.updated,
|
||||
regional: record.regional || false,
|
||||
format: record.format,
|
||||
phase: record.phase,
|
||||
group_config: record.group_config,
|
||||
teams,
|
||||
matches,
|
||||
groups,
|
||||
first_place,
|
||||
second_place,
|
||||
third_place,
|
||||
|
||||
@@ -5,6 +5,40 @@ import SuperTokens from "supertokens-node";
|
||||
|
||||
export async function getSessionForStart(request: Request, options?: { sessionRequired?: boolean }) {
|
||||
ensureSuperTokensBackend();
|
||||
|
||||
const cookieHeader = request.headers.get('cookie');
|
||||
if (cookieHeader) {
|
||||
const tokens = cookieHeader.match(/sAccessToken=([^;]+)/g);
|
||||
if (tokens && tokens.length > 1) {
|
||||
logger.warn(`Detected ${tokens.length} duplicate sAccessToken cookies, cleaning up`);
|
||||
|
||||
const parsedTokens = tokens.map(tokenStr => {
|
||||
const token = tokenStr.replace('sAccessToken=', '');
|
||||
try {
|
||||
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
||||
return { token, exp: payload.exp, iat: payload.iat };
|
||||
} catch (e) {
|
||||
logger.error('Failed to parse token', e);
|
||||
return { token, exp: 0, iat: 0 };
|
||||
}
|
||||
});
|
||||
|
||||
parsedTokens.sort((a, b) => b.exp - a.exp);
|
||||
const freshestToken = parsedTokens[0];
|
||||
|
||||
logger.info(`Using freshest token: exp=${freshestToken.exp}, iat=${freshestToken.iat}`);
|
||||
|
||||
const cleanedCookie = cookieHeader
|
||||
.split(';')
|
||||
.filter(c => !c.trim().startsWith('sAccessToken='))
|
||||
.join(';') + `; sAccessToken=${freshestToken.token}`;
|
||||
|
||||
const cleanedHeaders = new Headers(request.headers);
|
||||
cleanedHeaders.set('cookie', cleanedCookie);
|
||||
request = new Request(request, { headers: cleanedHeaders });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await getSessionForSSR(request);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export const backendConfig = (): TypeInput => {
|
||||
cookieSameSite: "lax",
|
||||
cookieSecure: process.env.NODE_ENV === "production",
|
||||
cookieDomain: process.env.COOKIE_DOMAIN || undefined,
|
||||
olderCookieDomain: undefined,
|
||||
antiCsrf: process.env.NODE_ENV === "production" ? "VIA_TOKEN" : "NONE",
|
||||
|
||||
// Debug only
|
||||
|
||||
@@ -2,6 +2,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import tsConfigPaths from 'vite-tsconfig-paths'
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
server: {
|
||||
@@ -18,6 +19,46 @@ export default defineConfig(({ mode }) => ({
|
||||
tanstackStart({
|
||||
srcDirectory: 'src/app',
|
||||
}),
|
||||
react()
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
manifest: false,
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'google-fonts-cache',
|
||||
expiration: {
|
||||
maxEntries: 10,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 365 // 1 year
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'gstatic-fonts-cache',
|
||||
expiration: {
|
||||
maxEntries: 10,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 365 // 1 year
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
devOptions: {
|
||||
enabled: false
|
||||
}
|
||||
})
|
||||
]
|
||||
}))
|
||||
|
||||