updated team-join-policy
This commit is contained in:
parent
b2d718738e
commit
2dc92c55c2
109
package-lock.json
generated
109
package-lock.json
generated
@ -15,7 +15,7 @@
|
||||
"@fortawesome/fontawesome-free": "^7.0.0",
|
||||
"@preline/dropdown": "^3.0.1",
|
||||
"@preline/tooltip": "^3.0.0",
|
||||
"@prisma/client": "^6.17.0",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"chart.js": "^4.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"csgo-sharecode": "^3.1.2",
|
||||
@ -60,7 +60,7 @@
|
||||
"@types/ws": "^8.18.1",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.0",
|
||||
"prisma": "^6.17.0",
|
||||
"prisma": "^6.17.1",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.19.4",
|
||||
@ -85,7 +85,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -123,6 +122,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@ -1577,7 +1577,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
@ -1598,9 +1597,9 @@
|
||||
"license": "Licensed under MIT and Preline UI Fair Use License"
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.0.tgz",
|
||||
"integrity": "sha512-b42mTLOdLEZ6e/igu8CLdccAUX9AwHknQQ1+pHOftnzDP2QoyZyFvcANqSLs5ockimFKJnV7Ljf+qrhNYf6oAg==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz",
|
||||
"integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@ -1620,9 +1619,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.0.tgz",
|
||||
"integrity": "sha512-k8tuChKpkO/Vj7ZEzaQMNflNGbaW4X0r8+PC+W2JaqVRdiS2+ORSv1SrDwNxsb8YyzIQJucXqLGZbgxD97ZhsQ==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz",
|
||||
"integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -1633,53 +1632,53 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.0.tgz",
|
||||
"integrity": "sha512-eE2CB32nr1hRqyLVnOAVY6c//iSJ/PN+Yfoa/2sEzLGpORaCg61d+nvdAkYSh+6Y2B8L4BVyzkRMANLD6nnC2g==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz",
|
||||
"integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.0.tgz",
|
||||
"integrity": "sha512-XhE9v3hDQTNgCYMjogcCYKi7HCEkZf9WwTGuXy8cmY8JUijvU0ap4M7pGLx4pBblkp5EwUsYzw1YLtH7yi0GZw==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz",
|
||||
"integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.0",
|
||||
"@prisma/engines-version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"@prisma/fetch-engine": "6.17.0",
|
||||
"@prisma/get-platform": "6.17.0"
|
||||
"@prisma/debug": "6.17.1",
|
||||
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||
"@prisma/fetch-engine": "6.17.1",
|
||||
"@prisma/get-platform": "6.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a.tgz",
|
||||
"integrity": "sha512-G0VU4uFDreATgTz4sh3dTtU2C+jn+J6c060ixavWZaUaSRZsNQhSPW26lbfez7GHzR02RGCdqs5UcSuGBC3yLw==",
|
||||
"version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz",
|
||||
"integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.0.tgz",
|
||||
"integrity": "sha512-YSl5R3WIAPrmshYPkaaszOsBIWRAovOCHn3y7gkTNGG51LjYW4pi6PFNkGouW6CA06qeTjTbGrDRCgFjnmVWDg==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz",
|
||||
"integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.0",
|
||||
"@prisma/engines-version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"@prisma/get-platform": "6.17.0"
|
||||
"@prisma/debug": "6.17.1",
|
||||
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||
"@prisma/get-platform": "6.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.0.tgz",
|
||||
"integrity": "sha512-3tEKChrnlmLXPd870oiVfRvj7vVKuxqP349hYaMDsbV4TZd3+lFqw8KTI2Tbq5DopamfNuNqhVCj+R6ZxKKYGQ==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz",
|
||||
"integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.0"
|
||||
"@prisma/debug": "6.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rtsao/scc": {
|
||||
@ -2068,6 +2067,7 @@
|
||||
"integrity": "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
@ -2085,6 +2085,7 @@
|
||||
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@ -2182,6 +2183,7 @@
|
||||
"integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.30.1",
|
||||
"@typescript-eslint/types": "8.30.1",
|
||||
@ -2615,6 +2617,7 @@
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -3283,7 +3286,6 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@ -3424,6 +3426,7 @@
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
@ -3880,6 +3883,7 @@
|
||||
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@ -4054,6 +4058,7 @@
|
||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.8",
|
||||
@ -5402,7 +5407,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
|
||||
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
@ -5831,7 +5835,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
@ -6019,6 +6022,7 @@
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.0.tgz",
|
||||
"integrity": "sha512-k0MgP6BsK8cZ73wRjMazl2y2UcXj49ZXLDEgx6BikWuby/CN+nh81qFFI16edgd7xYpe/jj2OZEIwCoqnzz0bQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@next/env": "15.3.0",
|
||||
"@swc/counter": "0.1.3",
|
||||
@ -6311,8 +6315,7 @@
|
||||
"version": "0.9.15",
|
||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
@ -6329,7 +6332,6 @@
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@ -6458,7 +6460,6 @@
|
||||
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz",
|
||||
"integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^10.13.0 || >=12.0.0"
|
||||
}
|
||||
@ -6745,7 +6746,6 @@
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
|
||||
"integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pretty-format": "^3.8.0"
|
||||
},
|
||||
@ -6776,19 +6776,19 @@
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.0.tgz",
|
||||
"integrity": "sha512-rcvldz98r+2bVCs0MldQCBaaVJRCj9Ew4IqphLATF89OJcSzwRQpwnKXR+W2+2VjK7/o2x3ffu5+2N3Muu6Dbw==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz",
|
||||
"integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.17.0",
|
||||
"@prisma/engines": "6.17.0"
|
||||
"@prisma/config": "6.17.1",
|
||||
"@prisma/engines": "6.17.1"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
@ -6902,6 +6902,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -6911,6 +6912,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@ -6980,8 +6982,7 @@
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.4",
|
||||
@ -7638,7 +7639,8 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz",
|
||||
"integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
@ -7695,6 +7697,7 @@
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -7920,6 +7923,7 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -8175,8 +8179,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
"@fortawesome/fontawesome-free": "^7.0.0",
|
||||
"@preline/dropdown": "^3.0.1",
|
||||
"@preline/tooltip": "^3.0.0",
|
||||
"@prisma/client": "^6.17.0",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"chart.js": "^4.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"csgo-sharecode": "^3.1.2",
|
||||
@ -66,7 +66,7 @@
|
||||
"@types/ws": "^8.18.1",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.0",
|
||||
"prisma": "^6.17.0",
|
||||
"prisma": "^6.17.1",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.19.4",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
'use client'
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState, useRef } from 'react'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import TeamCard from './TeamCard'
|
||||
import type { Team, Player } from '../../../types/team'
|
||||
@ -89,18 +89,41 @@ export default function NoTeamView({ initialTeams, initialInvitationMap }: Props
|
||||
if (currentSteamId && !isConnected) connect(currentSteamId)
|
||||
}, [currentSteamId, isConnected, connect])
|
||||
|
||||
useEffect(() => { fetchTeamsAndInvitations() }, [])
|
||||
useEffect(() => {
|
||||
// Nur nachladen, falls keine Initialdaten übergeben wurden
|
||||
if (!initialTeams?.length) fetchTeamsAndInvitations()
|
||||
}, [])
|
||||
|
||||
const teamsRef = useRef(teams)
|
||||
useEffect(() => { teamsRef.current = teams }, [teams])
|
||||
|
||||
const lastSigRef = useRef<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastEvent) return
|
||||
|
||||
// Signatur: Typ + die paar Payload-Felder, die sich bei uns ändern
|
||||
const sig = JSON.stringify({
|
||||
t: lastEvent.type,
|
||||
tid: lastEvent.payload?.teamId ?? null,
|
||||
jp: lastEvent.payload?.joinPolicy ?? null,
|
||||
f: lastEvent.payload?.filename ?? null,
|
||||
v: lastEvent.payload?.version ?? null,
|
||||
})
|
||||
if (lastSigRef.current === sig) return
|
||||
lastSigRef.current = sig
|
||||
|
||||
const { type, payload } = lastEvent
|
||||
if (TEAM_EVENTS.has(type)) {
|
||||
if (!payload?.teamId || teams.some(t => t.id === payload.teamId)) fetchTeamsAndInvitations()
|
||||
if (!payload?.teamId || teamsRef.current.some(t => t.id === payload.teamId)) {
|
||||
fetchTeamsAndInvitations()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (INVITE_EVENTS.has(type)) fetchTeamsAndInvitations()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [lastEvent, teams])
|
||||
if (INVITE_EVENTS.has(type)) {
|
||||
fetchTeamsAndInvitations()
|
||||
}
|
||||
}, [lastEvent])
|
||||
|
||||
const visibleTeams = useMemo(() => {
|
||||
const q = query.trim().toLowerCase()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// /src/app/components/TeamCard.tsx
|
||||
'use client'
|
||||
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import { useState, useMemo, useEffect, useRef } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import Button from './Button'
|
||||
import TeamPremierRankBadge from './TeamPremierRankBadge'
|
||||
@ -36,11 +36,10 @@ export default function TeamCard({
|
||||
|
||||
// ⬇️ NEU: lokale, “wirksame” Policy – startet mit Prop
|
||||
const [effectivePolicy, setEffectivePolicy] = useState<TeamJoinPolicy>(team.joinPolicy)
|
||||
|
||||
// Wenn sich Props ändern (neues Team oder Server-Refetch), Policy nachziehen
|
||||
useEffect(() => {
|
||||
setEffectivePolicy(team.joinPolicy)
|
||||
}, [team.id, team.joinPolicy])
|
||||
const sseWinsUntil = useRef(0)
|
||||
const lastHandledKeyRef = useRef('')
|
||||
|
||||
const lastSeenTsRef = useRef<number | null>(null)
|
||||
|
||||
// SSE-Verbindung herstellen
|
||||
useEffect(() => {
|
||||
@ -48,19 +47,39 @@ export default function TeamCard({
|
||||
if (!isConnected) connect(currentUserSteamId)
|
||||
}, [currentUserSteamId, isConnected, connect])
|
||||
|
||||
// Auf team-updated hören und ggf. Policy übernehmen
|
||||
// ⬇️ Jede 'team-updated'-Änderung verarbeiten, robust entpacken, per ts deduplizieren
|
||||
useEffect(() => {
|
||||
if (!lastEvent || !isSseEventType(lastEvent.type)) return
|
||||
if (lastEvent.type !== 'team-updated') return
|
||||
const ev = lastEvent
|
||||
if (!ev || ev.type !== 'team-updated') return
|
||||
|
||||
const payload = lastEvent.payload ?? {}
|
||||
if (payload.teamId !== team.id) return
|
||||
// payload kann entweder direkt die Felder haben … oder unter payload liegen
|
||||
const p = (ev.payload && typeof ev.payload === 'object' && 'payload' in ev.payload)
|
||||
? ev.payload.payload
|
||||
: ev.payload
|
||||
|
||||
const jp = payload.joinPolicy as TeamJoinPolicy | undefined
|
||||
const tid = p?.teamId
|
||||
const jp = p?.joinPolicy as TeamJoinPolicy | undefined
|
||||
if (tid !== team.id) return
|
||||
if (jp !== 'REQUEST' && jp !== 'INVITE_ONLY') return
|
||||
|
||||
// Dedupe an der Ereignis-Identität (ts stammt aus dem Store)
|
||||
if (ev.ts && lastSeenTsRef.current === ev.ts) return
|
||||
lastSeenTsRef.current = ev.ts ?? Date.now()
|
||||
|
||||
// kurzes Fenster, in dem Props-Refetch nicht wieder überschreibt
|
||||
sseWinsUntil.current = Date.now() + 1500
|
||||
setEffectivePolicy(jp)
|
||||
}, [lastEvent?.ts, lastEvent, team.id])
|
||||
|
||||
|
||||
// ⬇️ Props nur übernehmen, wenn kein frisches SSE dazwischenfunkt
|
||||
useEffect(() => {
|
||||
const jp = team.joinPolicy as TeamJoinPolicy | undefined
|
||||
if (Date.now() < sseWinsUntil.current) return
|
||||
if (jp === 'REQUEST' || jp === 'INVITE_ONLY') {
|
||||
setEffectivePolicy(jp)
|
||||
setEffectivePolicy(prev => (prev === jp ? prev : jp))
|
||||
}
|
||||
}, [lastEvent, team.id])
|
||||
}, [team.id, team.joinPolicy])
|
||||
|
||||
// ── Stati ableiten (jetzt von effectivePolicy!)
|
||||
const isInviteOnly = effectivePolicy === 'INVITE_ONLY'
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
// /src/app/[locale]/components/TeamMemberView.tsx
|
||||
|
||||
'use client'
|
||||
@ -42,8 +43,52 @@ type Props = {
|
||||
adminMode?: boolean
|
||||
}
|
||||
|
||||
export default function TeamMemberView({
|
||||
team: teamProp,
|
||||
/**
|
||||
* Wrapper-Komponente:
|
||||
* - spiegelt optionales team-Prop in den Store
|
||||
* - rendert Body erst, wenn team + Berechtigungen vorhanden sind
|
||||
* Dadurch bleiben Hooks-Reihenfolgen stabil.
|
||||
*/
|
||||
export default function TeamMemberView(props: Props) {
|
||||
const { team: storeTeam, setTeam } = useTeamStore()
|
||||
|
||||
// Prop -> Store spiegeln: auch bei gleicher ID relevante Felder patchen
|
||||
useEffect(() => {
|
||||
if (!props.team) return
|
||||
const curr = useTeamStore.getState().team
|
||||
if (!curr || curr.id !== props.team.id) {
|
||||
setTeam(props.team as Team)
|
||||
return
|
||||
}
|
||||
// gleiche ID → selektiv patchen
|
||||
const next = props.team as Team
|
||||
const diff: Partial<Team> = {}
|
||||
if (curr.name !== next.name) diff.name = next.name
|
||||
if (curr.logo !== next.logo) diff.logo = next.logo
|
||||
if ((curr.leader?.steamId ?? null) !== (next.leader?.steamId ?? null)) diff.leader = next.leader
|
||||
if (typeof next.joinPolicy === 'string' && curr.joinPolicy !== next.joinPolicy) {
|
||||
diff.joinPolicy = next.joinPolicy as any
|
||||
}
|
||||
if (Object.keys(diff).length) setTeam({ ...curr, ...diff } as Team)
|
||||
}, [props.team, setTeam])
|
||||
|
||||
// Guards dürfen im Wrapper stehen (kein Hook darunter bricht ab)
|
||||
if (!props.adminMode && !props.currentUserSteamId) return null
|
||||
|
||||
const team = props.team ?? storeTeam ?? null
|
||||
if (!team) return null
|
||||
|
||||
// Ab hier nur noch Body rendern – dort gibt es keine frühen Returns mehr vor Hooks
|
||||
return <TeamMemberViewBody {...props} team={team} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Body-Komponente:
|
||||
* - enthält ALLE übrigen Hooks in fester Reihenfolge
|
||||
* - hier ist team garantiert vorhanden (nicht null)
|
||||
*/
|
||||
function TeamMemberViewBody({
|
||||
team,
|
||||
activeDragItem,
|
||||
isDragging,
|
||||
showLeaveModal,
|
||||
@ -54,11 +99,9 @@ export default function TeamMemberView({
|
||||
setActiveDragItem,
|
||||
setIsDragging,
|
||||
adminMode = false,
|
||||
}: Props) {
|
||||
const { team: storeTeam, setTeam } = useTeamStore()
|
||||
const team = teamProp ?? storeTeam
|
||||
if (!team) return null
|
||||
|
||||
}: Props & { team: Team }) {
|
||||
const { setTeam } = useTeamStore()
|
||||
|
||||
const teamId = team.id
|
||||
const teamLeaderSteamId = team.leader?.steamId ?? ''
|
||||
|
||||
@ -88,8 +131,9 @@ export default function TeamMemberView({
|
||||
const [saveSuccess, setSaveSuccess] = useState(false)
|
||||
|
||||
const [joinPolicy, setJoinPolicy] = useState<TeamJoinPolicy>(
|
||||
(team as any).joinPolicy ?? 'REQUEST'
|
||||
(team.joinPolicy as TeamJoinPolicy) ?? 'REQUEST'
|
||||
)
|
||||
const policyChangedAtRef = useRef<number | null>(null)
|
||||
const [savingPolicy, setSavingPolicy] = useState(false)
|
||||
const [policySaved, setPolicySaved] = useState(false)
|
||||
|
||||
@ -128,14 +172,26 @@ export default function TeamMemberView({
|
||||
const bb = b.map(p=>p.steamId).join(',')
|
||||
return aa === bb
|
||||
}
|
||||
|
||||
|
||||
const eqSetByIds = (a: {steamId:string}[], b: {steamId:string}[]) => {
|
||||
if (a.length !== b.length) return false
|
||||
const sa = [...a.map(p => p.steamId)].sort()
|
||||
const sb = [...b.map(p => p.steamId)].sort()
|
||||
for (let i = 0; i < sa.length; i++) {
|
||||
if (sa[i] !== sb[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setJoinPolicy(((team as any)?.joinPolicy ?? 'REQUEST') as TeamJoinPolicy)
|
||||
}, [team?.id, (team as any)?.joinPolicy])
|
||||
// Nur setzen, wenn der Server wirklich einen Wert liefert.
|
||||
if (typeof team.joinPolicy === 'string') {
|
||||
setJoinPolicy(team.joinPolicy as TeamJoinPolicy)
|
||||
}
|
||||
}, [team.id, team.joinPolicy])
|
||||
|
||||
// Team-Listen lokal synchronisieren
|
||||
useEffect(() => {
|
||||
if (!team) return
|
||||
const nextActive = (team.activePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
|
||||
const nextInactive = (team.inactivePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
|
||||
const nextInvited = Array.from(new Map((team.invitedPlayers ?? []).map(p => [p.steamId, p])).values())
|
||||
@ -160,32 +216,50 @@ export default function TeamMemberView({
|
||||
|
||||
// Relevante SSE-Events
|
||||
useEffect(() => {
|
||||
if (!lastEvent || !teamId) return
|
||||
if (!lastEvent || !team.id) return
|
||||
if (!isSseEventType(lastEvent.type)) return
|
||||
const payload = lastEvent.payload ?? {}
|
||||
const now = Date.now()
|
||||
|
||||
// ► Spezialfall: nur Logo aktualisieren (ohne komplettes Reload)
|
||||
// nur joinPolicy geändert → minimal patchen
|
||||
if (lastEvent.type === 'team-updated' && payload.teamId === team.id) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Nach lokalem Speichern kommt oft ein generisches team-updated ohne joinPolicy.
|
||||
// Das würde ein Reload triggern → 1x kurz ignorieren.
|
||||
if (lastEvent.type === 'team-updated' && payload.teamId === team.id) {
|
||||
if (policyChangedAtRef.current && (now - policyChangedAtRef.current) < 2000) {
|
||||
policyChangedAtRef.current = null
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// nur Logo geändert → minimal patchen
|
||||
if (lastEvent.type === 'team-logo-updated') {
|
||||
if (payload.teamId && payload.teamId !== team.id) return
|
||||
|
||||
const current = useTeamStore.getState().team
|
||||
if (payload?.filename && current) {
|
||||
setTeam({ ...current, logo: payload.filename })
|
||||
}
|
||||
const curr = useTeamStore.getState().team
|
||||
if (payload?.filename && curr) setTeam({ ...curr, logo: payload.filename })
|
||||
if (payload?.version) setLogoVersion(payload.version)
|
||||
return
|
||||
}
|
||||
|
||||
// andere Team/Self-Events
|
||||
// Rest: reload + remount NUR wenn Listen wirklich anders sind
|
||||
if (!RELEVANT.has(lastEvent.type)) return
|
||||
if (payload.teamId && payload.teamId !== team.id) return
|
||||
|
||||
;(async () => {
|
||||
const updated = await reloadTeam(teamId)
|
||||
const updated = await reloadTeam(team.id)
|
||||
if (!updated) return
|
||||
|
||||
setTeam(updated)
|
||||
setEditedName(updated.name || '')
|
||||
|
||||
if (typeof (updated as any).joinPolicy === 'string') {
|
||||
setJoinPolicy((updated as any).joinPolicy as TeamJoinPolicy)
|
||||
}
|
||||
|
||||
const nextActive = (updated.activePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
|
||||
const nextInactive = (updated.inactivePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
|
||||
const nextInvited = Array.from(new Map((updated.invitedPlayers ?? []).map(p => [p.steamId, p])).values())
|
||||
@ -195,12 +269,33 @@ export default function TeamMemberView({
|
||||
setPendingRemote({ active: nextActive, inactive: nextInactive, invited: nextInvited })
|
||||
return
|
||||
}
|
||||
setActivePlayers(nextActive)
|
||||
setInactivePlayers(nextInactive)
|
||||
setInvitedPlayers(nextInvited)
|
||||
setRemountKey(k => k + 1)
|
||||
|
||||
// 1) Set-Vergleich (Inhalt)
|
||||
const contentChanged =
|
||||
!eqSetByIds(activePlayers, nextActive) ||
|
||||
!eqSetByIds(inactivePlayers, nextInactive) ||
|
||||
!eqSetByIds(invitedPlayers, nextInvited)
|
||||
|
||||
// 2) Reihenfolge-Vergleich (nur Order)
|
||||
const orderChanged =
|
||||
!eqByIds(activePlayers, nextActive) ||
|
||||
!eqByIds(inactivePlayers, nextInactive) ||
|
||||
!eqByIds(invitedPlayers, nextInvited)
|
||||
|
||||
if (contentChanged) {
|
||||
// IDs haben sich geändert → Listen setzen + DnD remounten (Keys bleiben!)
|
||||
setActivePlayers(nextActive)
|
||||
setInactivePlayers(nextInactive)
|
||||
setInvitedPlayers(nextInvited)
|
||||
setRemountKey(k => k + 1)
|
||||
} else if (orderChanged) {
|
||||
// Nur Reihenfolge/Sichtung anders → Listen setzen, aber KEIN remount
|
||||
setActivePlayers(nextActive)
|
||||
setInactivePlayers(nextInactive)
|
||||
setInvitedPlayers(nextInvited)
|
||||
}
|
||||
})()
|
||||
}, [lastEvent, teamId, setTeam])
|
||||
}, [lastEvent, team.id, setTeam, activePlayers, inactivePlayers, invitedPlayers])
|
||||
|
||||
const handleDragStart = (event: any) => {
|
||||
const id = event.active.id as string
|
||||
@ -226,19 +321,31 @@ export default function TeamMemberView({
|
||||
|
||||
useEffect(() => {
|
||||
if (!showPolicyMenu) return
|
||||
const onDocClick = (e: MouseEvent) => {
|
||||
|
||||
const onOutside = (e: PointerEvent) => {
|
||||
if (!policyMenuRef.current) return
|
||||
if (!policyMenuRef.current.contains(e.target as Node)) setShowPolicyMenu(false)
|
||||
if (!policyMenuRef.current.contains(e.target as Node)) {
|
||||
// Klick außerhalb: Menü schließen + Navigation/Drag verhindern
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setShowPolicyMenu(false)
|
||||
}
|
||||
}
|
||||
const onEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') setShowPolicyMenu(false) }
|
||||
document.addEventListener('mousedown', onDocClick)
|
||||
const onEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setShowPolicyMenu(false)
|
||||
}
|
||||
|
||||
// Capture-Phase, damit wir VOR Links/Drag reagieren
|
||||
document.addEventListener('pointerdown', onOutside, { capture: true })
|
||||
document.addEventListener('keydown', onEsc)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onDocClick)
|
||||
document.removeEventListener('pointerdown', onOutside, { capture: true })
|
||||
document.removeEventListener('keydown', onEsc)
|
||||
}
|
||||
}, [showPolicyMenu])
|
||||
|
||||
|
||||
const updateTeamMembers = async (teamId: string, active: Player[], inactive: Player[]) => {
|
||||
try {
|
||||
const res = await fetch('/api/team/update-players', {
|
||||
@ -396,24 +503,36 @@ export default function TeamMemberView({
|
||||
const prev = joinPolicy
|
||||
try {
|
||||
setSavingPolicy(true)
|
||||
|
||||
const res = await fetch('/api/team/update-join-policy', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin', // oder 'include' – same-origin reicht bei relativer URL
|
||||
credentials: 'same-origin',
|
||||
cache: 'no-store',
|
||||
body: JSON.stringify({ teamId, joinPolicy: next }),
|
||||
body: JSON.stringify({ teamId, joinPolicy: next }), // teamId aus dem Body-Scope
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
throw new Error(data?.message ?? `Speichern fehlgeschlagen (${res.status})`)
|
||||
}
|
||||
|
||||
const { joinPolicy: serverPolicy } = await res.json().catch(() => ({}))
|
||||
const patched = (serverPolicy ?? next) as TeamJoinPolicy
|
||||
|
||||
setJoinPolicy(patched)
|
||||
|
||||
// Store patchen
|
||||
const curr = useTeamStore.getState().team
|
||||
if (curr && curr.id === teamId && curr.joinPolicy !== patched) {
|
||||
setTeam({ ...curr, joinPolicy: patched })
|
||||
}
|
||||
|
||||
policyChangedAtRef.current = Date.now()
|
||||
|
||||
setPolicySaved(true)
|
||||
setTimeout(() => setPolicySaved(false), 2000)
|
||||
|
||||
const updated = await reloadTeam(teamId)
|
||||
if (updated) setTeam(updated)
|
||||
} catch (e) {
|
||||
// 🔙 Optimistisches Set zurückrollen
|
||||
setJoinPolicy(prev)
|
||||
console.error(e)
|
||||
alert((e as Error).message || 'Speichern fehlgeschlagen')
|
||||
@ -575,8 +694,6 @@ export default function TeamMemberView({
|
||||
})
|
||||
}
|
||||
|
||||
if (!adminMode && !currentUserSteamId) return null
|
||||
|
||||
const manageSteam: string = adminMode ? teamLeaderSteamId : currentUserSteamId
|
||||
|
||||
const renderMemberList = (players: Player[]) => (
|
||||
@ -784,7 +901,9 @@ export default function TeamMemberView({
|
||||
<div className="relative" ref={policyMenuRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPolicyMenu(v => !v)}
|
||||
onPointerDownCapture={(e) => { e.stopPropagation(); }} // verhindert Drag/Link schon sehr früh
|
||||
onMouseDown={(e) => e.stopPropagation()} // fallback
|
||||
onClick={(e) => { e.stopPropagation(); setShowPolicyMenu(v => !v) }}
|
||||
className="h-[32px] px-2.5 rounded-full text-xs border border-gray-300 dark:border-neutral-600
|
||||
bg-white dark:bg-neutral-800 text-gray-700 dark:text-neutral-200
|
||||
hover:bg-gray-100 hover:dark:bg-neutral-700 inline-flex items-center gap-1"
|
||||
@ -807,34 +926,44 @@ export default function TeamMemberView({
|
||||
</button>
|
||||
|
||||
{showPolicyMenu && (
|
||||
<div className="absolute right-0 z-10 mt-1 w-56 rounded-md border border-gray-200 dark:border-neutral-700
|
||||
bg-white dark:bg-neutral-800 shadow-lg p-1">
|
||||
<button
|
||||
onClick={() => applyPolicy('REQUEST')}
|
||||
className={`w-full text-left px-2.5 py-2 rounded-md text-sm
|
||||
${joinPolicy === 'REQUEST'
|
||||
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-neutral-700 text-gray-800 dark:text-neutral-200'}`}
|
||||
<>
|
||||
<div
|
||||
className="absolute right-0 z-[60] mt-1 w-56 rounded-md border border-gray-200
|
||||
dark:border-neutral-700 bg-white dark:bg-neutral-800 shadow-lg p-1"
|
||||
onPointerDownCapture={(e) => e.stopPropagation()} // Klicks bleiben im Menü
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="font-medium">Mit Genehmigung</div>
|
||||
<div className="text-xs text-gray-500 dark:text-neutral-400">
|
||||
Spieler stellen eine Anfrage; Leader entscheidet.
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onPointerDownCapture={(e) => e.stopPropagation()}
|
||||
onClick={(e) => { e.stopPropagation(); applyPolicy('REQUEST') }}
|
||||
className={`w-full text-left px-2.5 py-2 rounded-md text-sm
|
||||
${joinPolicy === 'REQUEST'
|
||||
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-neutral-700 text-gray-800 dark:text-neutral-200'}`}
|
||||
>
|
||||
<div className="font-medium">Mit Genehmigung</div>
|
||||
<div className="text-xs text-gray-500 dark:text-neutral-400">
|
||||
Spieler stellen eine Anfrage; Leader entscheidet.
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => applyPolicy('INVITE_ONLY')}
|
||||
className={`w-full text-left px-2.5 py-2 rounded-md text-sm
|
||||
${joinPolicy === 'INVITE_ONLY'
|
||||
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-neutral-700 text-gray-800 dark:text-neutral-200'}`}
|
||||
>
|
||||
<div className="font-medium">Nur Einladung</div>
|
||||
<div className="text-xs text-gray-500 dark:text-neutral-400">
|
||||
Beitritt nur per Einladung.
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onPointerDownCapture={(e) => e.stopPropagation()}
|
||||
onClick={(e) => { e.stopPropagation(); applyPolicy('INVITE_ONLY') }}
|
||||
className={`w-full text-left px-2.5 py-2 rounded-md text-sm
|
||||
${joinPolicy === 'INVITE_ONLY'
|
||||
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-neutral-700 text-gray-800 dark:text-neutral-200'}`}
|
||||
>
|
||||
<div className="font-medium">Nur Einladung</div>
|
||||
<div className="text-xs text-gray-500 dark:text-neutral-400">
|
||||
Beitritt nur per Einladung.
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* 🔼 Ende Policy-Pill */}
|
||||
|
||||
@ -68,7 +68,7 @@ export default function TeamPageClient() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card maxWidth="auto">
|
||||
<Card maxWidth="auto" bodyScrollable>
|
||||
<TeamCardComponent
|
||||
initialTeams={teams}
|
||||
initialInvitationMap={invitationMap}
|
||||
|
||||
@ -2,27 +2,26 @@
|
||||
import { NextResponse, type NextRequest } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { sessionAuthOptions } from '@/lib/auth' // ⬅️ hier umstellen
|
||||
import { sendServerSSEMessage } from '@/lib/sse-server-client'
|
||||
import type { TeamJoinPolicy } from '@/types/team'
|
||||
|
||||
export const runtime = 'nodejs' // ✅ Prisma-kompatibel
|
||||
export const dynamic = 'force-dynamic' // (nur Vorsicht, POST ist eh dynamisch)
|
||||
export const runtime = 'nodejs'
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const ALLOWED = ['REQUEST', 'INVITE_ONLY'] as const
|
||||
type AllowedPolicy = (typeof ALLOWED)[number]
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
// ─ Session ─
|
||||
const session = await getServerSession(authOptions(req))
|
||||
// ⬇️ statt getServerSession(authOptions(req))
|
||||
const session = await getServerSession(sessionAuthOptions)
|
||||
|
||||
const meId = session?.user?.steamId
|
||||
if (!meId) {
|
||||
return NextResponse.json({ message: 'Nicht eingeloggt' }, { status: 401 })
|
||||
}
|
||||
|
||||
// ─ Input ─
|
||||
const body = await req.json().catch(() => ({} as any))
|
||||
const teamId: string | undefined = body?.teamId
|
||||
const joinPolicy: TeamJoinPolicy | undefined = body?.joinPolicy
|
||||
@ -34,15 +33,10 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ message: 'Ungültige joinPolicy' }, { status: 400 })
|
||||
}
|
||||
|
||||
// ─ Daten ─
|
||||
const [team, me] = await Promise.all([
|
||||
prisma.team.findUnique({
|
||||
where: { id: teamId },
|
||||
select: {
|
||||
id: true,
|
||||
leaderId: true,
|
||||
joinPolicy: true,
|
||||
},
|
||||
select: { id: true, leaderId: true, joinPolicy: true },
|
||||
}),
|
||||
prisma.user.findUnique({
|
||||
where: { steamId: meId },
|
||||
@ -70,8 +64,7 @@ export async function POST(req: NextRequest) {
|
||||
select: { id: true, joinPolicy: true },
|
||||
})
|
||||
|
||||
// ─ SSE (nicht blockierend!) ─
|
||||
// Kein await → Request-Antwort wird NICHT aufgehalten
|
||||
// Fire-and-forget SSE
|
||||
Promise.resolve().then(() =>
|
||||
sendServerSSEMessage({
|
||||
type: 'team-updated',
|
||||
|
||||
@ -35,12 +35,12 @@ exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.17.0
|
||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
||||
* Prisma Client JS version: 6.16.3
|
||||
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.17.0",
|
||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
||||
client: "6.16.3",
|
||||
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||
@ -418,7 +418,7 @@ const config = {
|
||||
"value": "prisma-client-js"
|
||||
},
|
||||
"output": {
|
||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"fromEnvVar": null
|
||||
},
|
||||
"config": {
|
||||
@ -432,7 +432,7 @@ const config = {
|
||||
}
|
||||
],
|
||||
"previewFeatures": [],
|
||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
@ -440,8 +440,8 @@ const config = {
|
||||
"schemaEnvPath": "../../../.env"
|
||||
},
|
||||
"relativePath": "../../../prisma",
|
||||
"clientVersion": "6.17.0",
|
||||
"engineVersion": "c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"clientVersion": "6.16.3",
|
||||
"engineVersion": "bb420e667c1820a8c05a38023385f6cc7ef8e83a",
|
||||
"datasourceNames": [
|
||||
"db"
|
||||
],
|
||||
|
||||
@ -20,12 +20,12 @@ exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.17.0
|
||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
||||
* Prisma Client JS version: 6.16.3
|
||||
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.17.0",
|
||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
||||
client: "6.16.3",
|
||||
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
|
||||
4
src/generated/prisma/index.d.ts
vendored
4
src/generated/prisma/index.d.ts
vendored
@ -499,8 +499,8 @@ export namespace Prisma {
|
||||
export import Exact = $Public.Exact
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.17.0
|
||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
||||
* Prisma Client JS version: 6.16.3
|
||||
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||
*/
|
||||
export type PrismaVersion = {
|
||||
client: string
|
||||
|
||||
@ -35,12 +35,12 @@ exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.17.0
|
||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
||||
* Prisma Client JS version: 6.16.3
|
||||
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.17.0",
|
||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
||||
client: "6.16.3",
|
||||
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||
@ -419,7 +419,7 @@ const config = {
|
||||
"value": "prisma-client-js"
|
||||
},
|
||||
"output": {
|
||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"fromEnvVar": null
|
||||
},
|
||||
"config": {
|
||||
@ -433,7 +433,7 @@ const config = {
|
||||
}
|
||||
],
|
||||
"previewFeatures": [],
|
||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
@ -441,8 +441,8 @@ const config = {
|
||||
"schemaEnvPath": "../../../.env"
|
||||
},
|
||||
"relativePath": "../../../prisma",
|
||||
"clientVersion": "6.17.0",
|
||||
"engineVersion": "c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"clientVersion": "6.16.3",
|
||||
"engineVersion": "bb420e667c1820a8c05a38023385f6cc7ef8e83a",
|
||||
"datasourceNames": [
|
||||
"db"
|
||||
],
|
||||
|
||||
@ -151,7 +151,7 @@
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"version": "6.17.0",
|
||||
"version": "6.16.3",
|
||||
"sideEffects": false,
|
||||
"imports": {
|
||||
"#wasm-engine-loader": {
|
||||
|
||||
Binary file not shown.
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp24440
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp24440
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
src/generated/prisma/runtime/library.d.ts
vendored
1
src/generated/prisma/runtime/library.d.ts
vendored
@ -3062,7 +3062,6 @@ declare type QueryPlanNode = {
|
||||
args: {
|
||||
from: QueryPlanNode;
|
||||
to: QueryPlanNode;
|
||||
fields: string[];
|
||||
};
|
||||
} | {
|
||||
type: 'initializeRecord';
|
||||
|
||||
File diff suppressed because one or more lines are too long
10
src/generated/prisma/runtime/react-native.js
vendored
10
src/generated/prisma/runtime/react-native.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -35,12 +35,12 @@ exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.17.0
|
||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
||||
* Prisma Client JS version: 6.16.3
|
||||
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.17.0",
|
||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
||||
client: "6.16.3",
|
||||
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||
@ -418,7 +418,7 @@ const config = {
|
||||
"value": "prisma-client-js"
|
||||
},
|
||||
"output": {
|
||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"fromEnvVar": null
|
||||
},
|
||||
"config": {
|
||||
@ -432,7 +432,7 @@ const config = {
|
||||
}
|
||||
],
|
||||
"previewFeatures": [],
|
||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
@ -440,8 +440,8 @@ const config = {
|
||||
"schemaEnvPath": "../../../.env"
|
||||
},
|
||||
"relativePath": "../../../prisma",
|
||||
"clientVersion": "6.17.0",
|
||||
"engineVersion": "c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"clientVersion": "6.16.3",
|
||||
"engineVersion": "bb420e667c1820a8c05a38023385f6cc7ef8e83a",
|
||||
"datasourceNames": [
|
||||
"db"
|
||||
],
|
||||
|
||||
@ -21,6 +21,7 @@ function guessTzFromCountry(cc?: string | null): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
// 👉 Login-/Auth-Factory mit echtem Request (für /api/auth/... Routen, SignIn etc.)
|
||||
export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
providers: [
|
||||
@ -32,7 +33,6 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
||||
const steamProfile = profile as SteamProfile
|
||||
const location = steamProfile.loccountrycode ?? null
|
||||
|
||||
// create/update User (wie gehabt)
|
||||
const existing = await prisma.user.findUnique({
|
||||
where: { steamId: steamProfile.steamid },
|
||||
select: { timeZone: true },
|
||||
@ -68,7 +68,6 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
||||
token.image = steamProfile.avatarfull
|
||||
}
|
||||
|
||||
// DB laden & Flags
|
||||
const userInDb = await prisma.user.findUnique({
|
||||
where: { steamId: token.steamId || token.sub || '' },
|
||||
select: { teamId: true, isAdmin: true, steamId: true, timeZone: true },
|
||||
@ -82,7 +81,6 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
||||
token.timeZone = undefined
|
||||
}
|
||||
|
||||
// 🎯 Faceit-Sync ausgelagert
|
||||
if (token.steamId) {
|
||||
await syncFaceitProfile(prisma, token.steamId)
|
||||
}
|
||||
@ -97,10 +95,12 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
||||
steamId: token.steamId,
|
||||
name: token.name,
|
||||
image: token.image,
|
||||
team: token.team ?? null,
|
||||
isAdmin: token.isAdmin ?? false,
|
||||
team: (token as any).team ?? null,
|
||||
isAdmin: (token as any).isAdmin ?? false,
|
||||
timeZone: (token as any).timeZone ?? null,
|
||||
} as typeof session.user & { steamId: string; team: string | null; isAdmin: boolean; timeZone: string | null }
|
||||
} as typeof session.user & {
|
||||
steamId: string; team: string | null; isAdmin: boolean; timeZone: string | null
|
||||
}
|
||||
return session
|
||||
},
|
||||
|
||||
@ -112,7 +112,31 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
||||
return url.startsWith(baseUrl) ? url : baseUrl;
|
||||
},
|
||||
},
|
||||
session: { strategy: 'jwt' },
|
||||
})
|
||||
|
||||
// ➕ Base config
|
||||
export const baseAuthOptions: NextAuthOptions = authOptions({} as NextRequest)
|
||||
// ⚠️ NEU: Minimal-Options NUR für getServerSession() in App-Routen/Server Actions
|
||||
// → Kein Provider nötig (wir lesen nur die vorhandene Session)
|
||||
export const sessionAuthOptions: NextAuthOptions = {
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
providers: [], // ✅ satisfies NextAuthOptions, triggert keine Provider-Init
|
||||
session: { strategy: 'jwt' },
|
||||
callbacks: {
|
||||
async session({ session, token }) {
|
||||
if (!token.steamId) throw new Error('steamId is missing in token')
|
||||
session.user = {
|
||||
...session.user,
|
||||
steamId: token.steamId,
|
||||
name: token.name,
|
||||
image: token.image,
|
||||
team: (token as any).team ?? null,
|
||||
isAdmin: (token as any).isAdmin ?? false,
|
||||
timeZone: (token as any).timeZone ?? null,
|
||||
} as any
|
||||
return session
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ❌ WEG DAMIT: Das verursacht den Crash beim Import!
|
||||
// export const baseAuthOptions: NextAuthOptions = authOptions({} as NextRequest)
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
// middleware.ts
|
||||
import {NextResponse} from 'next/server';
|
||||
import type {NextRequest} from 'next/server';
|
||||
import createIntlMiddleware from 'next-intl/middleware';
|
||||
import {getToken} from 'next-auth/jwt';
|
||||
import {routing} from './i18n/routing';
|
||||
|
||||
// 1) i18n-Middleware vorbereiten
|
||||
// 1) i18n-Middleware vorbereiten (aber NICHT für /api etc. benutzen)
|
||||
const handleI18n = createIntlMiddleware(routing);
|
||||
|
||||
// Helpers
|
||||
// Helpers (deine bleiben unverändert)
|
||||
function getCurrentLocaleFromPath(pathname: string, locales: readonly string[], fallback: string) {
|
||||
const first = pathname.split('/')[1];
|
||||
return locales.includes(first) ? first : fallback;
|
||||
}
|
||||
|
||||
function stripLeadingLocale(pathname: string, locales: readonly string[]) {
|
||||
const parts = pathname.split('/');
|
||||
const first = parts[1];
|
||||
@ -22,33 +22,44 @@ function stripLeadingLocale(pathname: string, locales: readonly string[]) {
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
|
||||
function isProtectedPath(pathnameNoLocale: string) {
|
||||
return (
|
||||
pathnameNoLocale.startsWith('/dashboard') ||
|
||||
pathnameNoLocale.startsWith('/settings') ||
|
||||
pathnameNoLocale.startsWith('/matches') ||
|
||||
pathnameNoLocale.startsWith('/team') || // ← hinzugefügt
|
||||
pathnameNoLocale.startsWith('/team') ||
|
||||
pathnameNoLocale.startsWith('/admin')
|
||||
);
|
||||
}
|
||||
|
||||
export default async function middleware(req: NextRequest) {
|
||||
// 2) Erst i18n arbeiten lassen
|
||||
const i18nRes = handleI18n(req);
|
||||
const { pathname } = req.nextUrl;
|
||||
|
||||
// Wenn i18n bereits redirect/rewrite auslöst, direkt zurückgeben
|
||||
// 🚫 0) Harte Früh-Rückgaben: API & Statics NIE lokalisieren / autorisieren
|
||||
if (
|
||||
pathname.startsWith('/api') ||
|
||||
pathname.startsWith('/trpc') ||
|
||||
pathname.startsWith('/_next') ||
|
||||
pathname.startsWith('/_vercel') ||
|
||||
pathname.startsWith('/assets') ||
|
||||
pathname === '/favicon.ico' ||
|
||||
pathname === '/robots.txt' ||
|
||||
pathname === '/sitemap.xml' ||
|
||||
/\.[^/]+$/.test(pathname) // irgend eine Dateiendung
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// ✅ 1) i18n nur für *echte* Seiten laufen lassen
|
||||
const i18nRes = handleI18n(req);
|
||||
if (i18nRes.headers.get('location') || i18nRes.headers.get('x-middleware-rewrite')) {
|
||||
return i18nRes;
|
||||
}
|
||||
|
||||
// ✅ 2) Ab hier deine Auth-Logik nur für geschützte Seiten
|
||||
const {locales, defaultLocale} = routing;
|
||||
const url = req.nextUrl;
|
||||
const pathname = url.pathname;
|
||||
const currentLocale = getCurrentLocaleFromPath(pathname, locales, defaultLocale);
|
||||
const pathnameNoLocale = stripLeadingLocale(pathname, locales);
|
||||
|
||||
// 3) Nur für geschützte Pfade Auth prüfen
|
||||
if (!isProtectedPath(pathnameNoLocale)) {
|
||||
return i18nRes;
|
||||
}
|
||||
@ -58,6 +69,7 @@ export default async function middleware(req: NextRequest) {
|
||||
// Adminschutz
|
||||
if (pathnameNoLocale.startsWith('/admin')) {
|
||||
if (!token || !(token as any).isAdmin) {
|
||||
const currentLocale = getCurrentLocaleFromPath(pathname, locales, defaultLocale);
|
||||
const redirectUrl = url.clone();
|
||||
redirectUrl.pathname = `/${currentLocale}/dashboard`;
|
||||
return NextResponse.redirect(redirectUrl);
|
||||
@ -67,15 +79,16 @@ export default async function middleware(req: NextRequest) {
|
||||
// Allgemeiner Auth-Schutz
|
||||
if (!token) {
|
||||
const loginUrl = new URL('/api/auth/signin', req.url);
|
||||
loginUrl.searchParams.set('callbackUrl', url.toString()); // komplette Ziel-URL inkl. Locale
|
||||
loginUrl.searchParams.set('callbackUrl', url.toString());
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
// Alles gut → weiter
|
||||
return i18nRes;
|
||||
}
|
||||
|
||||
// Matcher robuster halten (gut so – ich würde assets & favicon ergänzen)
|
||||
export const config = {
|
||||
// Standard: alles außer /api, _next, statische Dateien
|
||||
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
|
||||
matcher: [
|
||||
'/((?!api|trpc|_next|_vercel|assets|favicon.ico|robots.txt|sitemap.xml|.*\\..*).*)'
|
||||
]
|
||||
};
|
||||
|
||||
@ -38,4 +38,6 @@ export type Team = {
|
||||
inactivePlayers: Player[]
|
||||
invitedPlayers: InvitedPlayer[]
|
||||
players?: MatchPlayer[]
|
||||
logoUpdatedAt?: string | Date | null
|
||||
updatedAt?: string | Date | null
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user