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",
|
"@fortawesome/fontawesome-free": "^7.0.0",
|
||||||
"@preline/dropdown": "^3.0.1",
|
"@preline/dropdown": "^3.0.1",
|
||||||
"@preline/tooltip": "^3.0.0",
|
"@preline/tooltip": "^3.0.0",
|
||||||
"@prisma/client": "^6.17.0",
|
"@prisma/client": "^6.17.1",
|
||||||
"chart.js": "^4.5.0",
|
"chart.js": "^4.5.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"csgo-sharecode": "^3.1.2",
|
"csgo-sharecode": "^3.1.2",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.3.0",
|
"eslint-config-next": "15.3.0",
|
||||||
"prisma": "^6.17.0",
|
"prisma": "^6.17.1",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsx": "^4.19.4",
|
"tsx": "^4.19.4",
|
||||||
@ -85,7 +85,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||||
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@ -123,6 +122,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/accessibility": "^3.1.1",
|
"@dnd-kit/accessibility": "^3.1.1",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@ -1577,7 +1577,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||||
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/panva"
|
"url": "https://github.com/sponsors/panva"
|
||||||
}
|
}
|
||||||
@ -1598,9 +1597,9 @@
|
|||||||
"license": "Licensed under MIT and Preline UI Fair Use License"
|
"license": "Licensed under MIT and Preline UI Fair Use License"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/client": {
|
"node_modules/@prisma/client": {
|
||||||
"version": "6.17.0",
|
"version": "6.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz",
|
||||||
"integrity": "sha512-b42mTLOdLEZ6e/igu8CLdccAUX9AwHknQQ1+pHOftnzDP2QoyZyFvcANqSLs5ockimFKJnV7Ljf+qrhNYf6oAg==",
|
"integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1620,9 +1619,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/config": {
|
"node_modules/@prisma/config": {
|
||||||
"version": "6.17.0",
|
"version": "6.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz",
|
||||||
"integrity": "sha512-k8tuChKpkO/Vj7ZEzaQMNflNGbaW4X0r8+PC+W2JaqVRdiS2+ORSv1SrDwNxsb8YyzIQJucXqLGZbgxD97ZhsQ==",
|
"integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1633,53 +1632,53 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/debug": {
|
"node_modules/@prisma/debug": {
|
||||||
"version": "6.17.0",
|
"version": "6.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz",
|
||||||
"integrity": "sha512-eE2CB32nr1hRqyLVnOAVY6c//iSJ/PN+Yfoa/2sEzLGpORaCg61d+nvdAkYSh+6Y2B8L4BVyzkRMANLD6nnC2g==",
|
"integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines": {
|
"node_modules/@prisma/engines": {
|
||||||
"version": "6.17.0",
|
"version": "6.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz",
|
||||||
"integrity": "sha512-XhE9v3hDQTNgCYMjogcCYKi7HCEkZf9WwTGuXy8cmY8JUijvU0ap4M7pGLx4pBblkp5EwUsYzw1YLtH7yi0GZw==",
|
"integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.17.0",
|
"@prisma/debug": "6.17.1",
|
||||||
"@prisma/engines-version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||||
"@prisma/fetch-engine": "6.17.0",
|
"@prisma/fetch-engine": "6.17.1",
|
||||||
"@prisma/get-platform": "6.17.0"
|
"@prisma/get-platform": "6.17.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines-version": {
|
"node_modules/@prisma/engines-version": {
|
||||||
"version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
"version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz",
|
||||||
"integrity": "sha512-G0VU4uFDreATgTz4sh3dTtU2C+jn+J6c060ixavWZaUaSRZsNQhSPW26lbfez7GHzR02RGCdqs5UcSuGBC3yLw==",
|
"integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/fetch-engine": {
|
"node_modules/@prisma/fetch-engine": {
|
||||||
"version": "6.17.0",
|
"version": "6.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz",
|
||||||
"integrity": "sha512-YSl5R3WIAPrmshYPkaaszOsBIWRAovOCHn3y7gkTNGG51LjYW4pi6PFNkGouW6CA06qeTjTbGrDRCgFjnmVWDg==",
|
"integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.17.0",
|
"@prisma/debug": "6.17.1",
|
||||||
"@prisma/engines-version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||||
"@prisma/get-platform": "6.17.0"
|
"@prisma/get-platform": "6.17.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/get-platform": {
|
"node_modules/@prisma/get-platform": {
|
||||||
"version": "6.17.0",
|
"version": "6.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz",
|
||||||
"integrity": "sha512-3tEKChrnlmLXPd870oiVfRvj7vVKuxqP349hYaMDsbV4TZd3+lFqw8KTI2Tbq5DopamfNuNqhVCj+R6ZxKKYGQ==",
|
"integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.17.0"
|
"@prisma/debug": "6.17.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
@ -2068,6 +2067,7 @@
|
|||||||
"integrity": "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw==",
|
"integrity": "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
@ -2085,6 +2085,7 @@
|
|||||||
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
|
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@ -2182,6 +2183,7 @@
|
|||||||
"integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
|
"integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.30.1",
|
"@typescript-eslint/scope-manager": "8.30.1",
|
||||||
"@typescript-eslint/types": "8.30.1",
|
"@typescript-eslint/types": "8.30.1",
|
||||||
@ -2615,6 +2617,7 @@
|
|||||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@ -3283,7 +3286,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@ -3424,6 +3426,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/kossnocorp"
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
@ -3880,6 +3883,7 @@
|
|||||||
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
|
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@ -4054,6 +4058,7 @@
|
|||||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.8",
|
"array-includes": "^3.1.8",
|
||||||
@ -5402,7 +5407,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
|
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
|
||||||
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
|
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/panva"
|
"url": "https://github.com/sponsors/panva"
|
||||||
}
|
}
|
||||||
@ -5831,7 +5835,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
},
|
},
|
||||||
@ -6019,6 +6022,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.3.0.tgz",
|
||||||
"integrity": "sha512-k0MgP6BsK8cZ73wRjMazl2y2UcXj49ZXLDEgx6BikWuby/CN+nh81qFFI16edgd7xYpe/jj2OZEIwCoqnzz0bQ==",
|
"integrity": "sha512-k0MgP6BsK8cZ73wRjMazl2y2UcXj49ZXLDEgx6BikWuby/CN+nh81qFFI16edgd7xYpe/jj2OZEIwCoqnzz0bQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.3.0",
|
"@next/env": "15.3.0",
|
||||||
"@swc/counter": "0.1.3",
|
"@swc/counter": "0.1.3",
|
||||||
@ -6311,8 +6315,7 @@
|
|||||||
"version": "0.9.15",
|
"version": "0.9.15",
|
||||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||||
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==",
|
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@ -6329,7 +6332,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||||
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
@ -6458,7 +6460,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz",
|
||||||
"integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==",
|
"integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10.13.0 || >=12.0.0"
|
"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",
|
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
|
||||||
"integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
|
"integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pretty-format": "^3.8.0"
|
"pretty-format": "^3.8.0"
|
||||||
},
|
},
|
||||||
@ -6776,19 +6776,19 @@
|
|||||||
"version": "3.8.0",
|
"version": "3.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||||
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/prisma": {
|
"node_modules/prisma": {
|
||||||
"version": "6.17.0",
|
"version": "6.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz",
|
||||||
"integrity": "sha512-rcvldz98r+2bVCs0MldQCBaaVJRCj9Ew4IqphLATF89OJcSzwRQpwnKXR+W2+2VjK7/o2x3ffu5+2N3Muu6Dbw==",
|
"integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/config": "6.17.0",
|
"@prisma/config": "6.17.1",
|
||||||
"@prisma/engines": "6.17.0"
|
"@prisma/engines": "6.17.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"prisma": "build/index.js"
|
"prisma": "build/index.js"
|
||||||
@ -6902,6 +6902,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -6911,6 +6912,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@ -6980,8 +6982,7 @@
|
|||||||
"version": "0.14.1",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
@ -7638,7 +7639,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz",
|
||||||
"integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==",
|
"integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
@ -7695,6 +7697,7 @@
|
|||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@ -7920,6 +7923,7 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@ -8175,8 +8179,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"license": "ISC",
|
"license": "ISC"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/yn": {
|
"node_modules/yn": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^7.0.0",
|
"@fortawesome/fontawesome-free": "^7.0.0",
|
||||||
"@preline/dropdown": "^3.0.1",
|
"@preline/dropdown": "^3.0.1",
|
||||||
"@preline/tooltip": "^3.0.0",
|
"@preline/tooltip": "^3.0.0",
|
||||||
"@prisma/client": "^6.17.0",
|
"@prisma/client": "^6.17.1",
|
||||||
"chart.js": "^4.5.0",
|
"chart.js": "^4.5.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"csgo-sharecode": "^3.1.2",
|
"csgo-sharecode": "^3.1.2",
|
||||||
@ -66,7 +66,7 @@
|
|||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.3.0",
|
"eslint-config-next": "15.3.0",
|
||||||
"prisma": "^6.17.0",
|
"prisma": "^6.17.1",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsx": "^4.19.4",
|
"tsx": "^4.19.4",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState, useRef } from 'react'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import TeamCard from './TeamCard'
|
import TeamCard from './TeamCard'
|
||||||
import type { Team, Player } from '../../../types/team'
|
import type { Team, Player } from '../../../types/team'
|
||||||
@ -89,18 +89,41 @@ export default function NoTeamView({ initialTeams, initialInvitationMap }: Props
|
|||||||
if (currentSteamId && !isConnected) connect(currentSteamId)
|
if (currentSteamId && !isConnected) connect(currentSteamId)
|
||||||
}, [currentSteamId, isConnected, connect])
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!lastEvent) return
|
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
|
const { type, payload } = lastEvent
|
||||||
if (TEAM_EVENTS.has(type)) {
|
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
|
return
|
||||||
}
|
}
|
||||||
if (INVITE_EVENTS.has(type)) fetchTeamsAndInvitations()
|
if (INVITE_EVENTS.has(type)) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
fetchTeamsAndInvitations()
|
||||||
}, [lastEvent, teams])
|
}
|
||||||
|
}, [lastEvent])
|
||||||
|
|
||||||
const visibleTeams = useMemo(() => {
|
const visibleTeams = useMemo(() => {
|
||||||
const q = query.trim().toLowerCase()
|
const q = query.trim().toLowerCase()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// /src/app/components/TeamCard.tsx
|
// /src/app/components/TeamCard.tsx
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useMemo, useEffect } from 'react'
|
import { useState, useMemo, useEffect, useRef } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import TeamPremierRankBadge from './TeamPremierRankBadge'
|
import TeamPremierRankBadge from './TeamPremierRankBadge'
|
||||||
@ -36,11 +36,10 @@ export default function TeamCard({
|
|||||||
|
|
||||||
// ⬇️ NEU: lokale, “wirksame” Policy – startet mit Prop
|
// ⬇️ NEU: lokale, “wirksame” Policy – startet mit Prop
|
||||||
const [effectivePolicy, setEffectivePolicy] = useState<TeamJoinPolicy>(team.joinPolicy)
|
const [effectivePolicy, setEffectivePolicy] = useState<TeamJoinPolicy>(team.joinPolicy)
|
||||||
|
const sseWinsUntil = useRef(0)
|
||||||
|
const lastHandledKeyRef = useRef('')
|
||||||
|
|
||||||
// Wenn sich Props ändern (neues Team oder Server-Refetch), Policy nachziehen
|
const lastSeenTsRef = useRef<number | null>(null)
|
||||||
useEffect(() => {
|
|
||||||
setEffectivePolicy(team.joinPolicy)
|
|
||||||
}, [team.id, team.joinPolicy])
|
|
||||||
|
|
||||||
// SSE-Verbindung herstellen
|
// SSE-Verbindung herstellen
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -48,19 +47,39 @@ export default function TeamCard({
|
|||||||
if (!isConnected) connect(currentUserSteamId)
|
if (!isConnected) connect(currentUserSteamId)
|
||||||
}, [currentUserSteamId, isConnected, connect])
|
}, [currentUserSteamId, isConnected, connect])
|
||||||
|
|
||||||
// Auf team-updated hören und ggf. Policy übernehmen
|
// ⬇️ Jede 'team-updated'-Änderung verarbeiten, robust entpacken, per ts deduplizieren
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lastEvent || !isSseEventType(lastEvent.type)) return
|
const ev = lastEvent
|
||||||
if (lastEvent.type !== 'team-updated') return
|
if (!ev || ev.type !== 'team-updated') return
|
||||||
|
|
||||||
const payload = lastEvent.payload ?? {}
|
// payload kann entweder direkt die Felder haben … oder unter payload liegen
|
||||||
if (payload.teamId !== team.id) return
|
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
|
||||||
if (jp === 'REQUEST' || jp === 'INVITE_ONLY') {
|
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)
|
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(prev => (prev === jp ? prev : jp))
|
||||||
}
|
}
|
||||||
}, [lastEvent, team.id])
|
}, [team.id, team.joinPolicy])
|
||||||
|
|
||||||
// ── Stati ableiten (jetzt von effectivePolicy!)
|
// ── Stati ableiten (jetzt von effectivePolicy!)
|
||||||
const isInviteOnly = effectivePolicy === 'INVITE_ONLY'
|
const isInviteOnly = effectivePolicy === 'INVITE_ONLY'
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
// /src/app/[locale]/components/TeamMemberView.tsx
|
// /src/app/[locale]/components/TeamMemberView.tsx
|
||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
@ -42,8 +43,52 @@ type Props = {
|
|||||||
adminMode?: boolean
|
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,
|
activeDragItem,
|
||||||
isDragging,
|
isDragging,
|
||||||
showLeaveModal,
|
showLeaveModal,
|
||||||
@ -54,10 +99,8 @@ export default function TeamMemberView({
|
|||||||
setActiveDragItem,
|
setActiveDragItem,
|
||||||
setIsDragging,
|
setIsDragging,
|
||||||
adminMode = false,
|
adminMode = false,
|
||||||
}: Props) {
|
}: Props & { team: Team }) {
|
||||||
const { team: storeTeam, setTeam } = useTeamStore()
|
const { setTeam } = useTeamStore()
|
||||||
const team = teamProp ?? storeTeam
|
|
||||||
if (!team) return null
|
|
||||||
|
|
||||||
const teamId = team.id
|
const teamId = team.id
|
||||||
const teamLeaderSteamId = team.leader?.steamId ?? ''
|
const teamLeaderSteamId = team.leader?.steamId ?? ''
|
||||||
@ -88,8 +131,9 @@ export default function TeamMemberView({
|
|||||||
const [saveSuccess, setSaveSuccess] = useState(false)
|
const [saveSuccess, setSaveSuccess] = useState(false)
|
||||||
|
|
||||||
const [joinPolicy, setJoinPolicy] = useState<TeamJoinPolicy>(
|
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 [savingPolicy, setSavingPolicy] = useState(false)
|
||||||
const [policySaved, setPolicySaved] = useState(false)
|
const [policySaved, setPolicySaved] = useState(false)
|
||||||
|
|
||||||
@ -129,13 +173,25 @@ export default function TeamMemberView({
|
|||||||
return aa === bb
|
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(() => {
|
useEffect(() => {
|
||||||
setJoinPolicy(((team as any)?.joinPolicy ?? 'REQUEST') as TeamJoinPolicy)
|
// Nur setzen, wenn der Server wirklich einen Wert liefert.
|
||||||
}, [team?.id, (team as any)?.joinPolicy])
|
if (typeof team.joinPolicy === 'string') {
|
||||||
|
setJoinPolicy(team.joinPolicy as TeamJoinPolicy)
|
||||||
|
}
|
||||||
|
}, [team.id, team.joinPolicy])
|
||||||
|
|
||||||
// Team-Listen lokal synchronisieren
|
// Team-Listen lokal synchronisieren
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!team) return
|
|
||||||
const nextActive = (team.activePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
|
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 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())
|
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
|
// Relevante SSE-Events
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lastEvent || !teamId) return
|
if (!lastEvent || !team.id) return
|
||||||
if (!isSseEventType(lastEvent.type)) return
|
if (!isSseEventType(lastEvent.type)) return
|
||||||
const payload = lastEvent.payload ?? {}
|
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 (lastEvent.type === 'team-logo-updated') {
|
||||||
if (payload.teamId && payload.teamId !== team.id) return
|
if (payload.teamId && payload.teamId !== team.id) return
|
||||||
|
const curr = useTeamStore.getState().team
|
||||||
const current = useTeamStore.getState().team
|
if (payload?.filename && curr) setTeam({ ...curr, logo: payload.filename })
|
||||||
if (payload?.filename && current) {
|
|
||||||
setTeam({ ...current, logo: payload.filename })
|
|
||||||
}
|
|
||||||
if (payload?.version) setLogoVersion(payload.version)
|
if (payload?.version) setLogoVersion(payload.version)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// andere Team/Self-Events
|
// Rest: reload + remount NUR wenn Listen wirklich anders sind
|
||||||
if (!RELEVANT.has(lastEvent.type)) return
|
if (!RELEVANT.has(lastEvent.type)) return
|
||||||
if (payload.teamId && payload.teamId !== team.id) return
|
if (payload.teamId && payload.teamId !== team.id) return
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const updated = await reloadTeam(teamId)
|
const updated = await reloadTeam(team.id)
|
||||||
if (!updated) return
|
if (!updated) return
|
||||||
|
|
||||||
setTeam(updated)
|
setTeam(updated)
|
||||||
setEditedName(updated.name || '')
|
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 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 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())
|
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 })
|
setPendingRemote({ active: nextActive, inactive: nextInactive, invited: nextInvited })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
setActivePlayers(nextActive)
|
||||||
setInactivePlayers(nextInactive)
|
setInactivePlayers(nextInactive)
|
||||||
setInvitedPlayers(nextInvited)
|
setInvitedPlayers(nextInvited)
|
||||||
setRemountKey(k => k + 1)
|
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 handleDragStart = (event: any) => {
|
||||||
const id = event.active.id as string
|
const id = event.active.id as string
|
||||||
@ -226,19 +321,31 @@ export default function TeamMemberView({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showPolicyMenu) return
|
if (!showPolicyMenu) return
|
||||||
const onDocClick = (e: MouseEvent) => {
|
|
||||||
|
const onOutside = (e: PointerEvent) => {
|
||||||
if (!policyMenuRef.current) return
|
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)
|
document.addEventListener('keydown', onEsc)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousedown', onDocClick)
|
document.removeEventListener('pointerdown', onOutside, { capture: true })
|
||||||
document.removeEventListener('keydown', onEsc)
|
document.removeEventListener('keydown', onEsc)
|
||||||
}
|
}
|
||||||
}, [showPolicyMenu])
|
}, [showPolicyMenu])
|
||||||
|
|
||||||
|
|
||||||
const updateTeamMembers = async (teamId: string, active: Player[], inactive: Player[]) => {
|
const updateTeamMembers = async (teamId: string, active: Player[], inactive: Player[]) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/team/update-players', {
|
const res = await fetch('/api/team/update-players', {
|
||||||
@ -396,24 +503,36 @@ export default function TeamMemberView({
|
|||||||
const prev = joinPolicy
|
const prev = joinPolicy
|
||||||
try {
|
try {
|
||||||
setSavingPolicy(true)
|
setSavingPolicy(true)
|
||||||
|
|
||||||
const res = await fetch('/api/team/update-join-policy', {
|
const res = await fetch('/api/team/update-join-policy', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
credentials: 'same-origin', // oder 'include' – same-origin reicht bei relativer URL
|
credentials: 'same-origin',
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
body: JSON.stringify({ teamId, joinPolicy: next }),
|
body: JSON.stringify({ teamId, joinPolicy: next }), // teamId aus dem Body-Scope
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json().catch(() => ({}))
|
const data = await res.json().catch(() => ({}))
|
||||||
throw new Error(data?.message ?? `Speichern fehlgeschlagen (${res.status})`)
|
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)
|
setPolicySaved(true)
|
||||||
setTimeout(() => setPolicySaved(false), 2000)
|
setTimeout(() => setPolicySaved(false), 2000)
|
||||||
|
|
||||||
const updated = await reloadTeam(teamId)
|
|
||||||
if (updated) setTeam(updated)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 🔙 Optimistisches Set zurückrollen
|
|
||||||
setJoinPolicy(prev)
|
setJoinPolicy(prev)
|
||||||
console.error(e)
|
console.error(e)
|
||||||
alert((e as Error).message || 'Speichern fehlgeschlagen')
|
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 manageSteam: string = adminMode ? teamLeaderSteamId : currentUserSteamId
|
||||||
|
|
||||||
const renderMemberList = (players: Player[]) => (
|
const renderMemberList = (players: Player[]) => (
|
||||||
@ -784,7 +901,9 @@ export default function TeamMemberView({
|
|||||||
<div className="relative" ref={policyMenuRef}>
|
<div className="relative" ref={policyMenuRef}>
|
||||||
<button
|
<button
|
||||||
type="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
|
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
|
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"
|
hover:bg-gray-100 hover:dark:bg-neutral-700 inline-flex items-center gap-1"
|
||||||
@ -807,10 +926,17 @@ export default function TeamMemberView({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showPolicyMenu && (
|
{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">
|
<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()}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => applyPolicy('REQUEST')}
|
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
|
className={`w-full text-left px-2.5 py-2 rounded-md text-sm
|
||||||
${joinPolicy === 'REQUEST'
|
${joinPolicy === 'REQUEST'
|
||||||
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
||||||
@ -823,7 +949,9 @@ export default function TeamMemberView({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => applyPolicy('INVITE_ONLY')}
|
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
|
className={`w-full text-left px-2.5 py-2 rounded-md text-sm
|
||||||
${joinPolicy === 'INVITE_ONLY'
|
${joinPolicy === 'INVITE_ONLY'
|
||||||
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
|
||||||
@ -835,6 +963,7 @@ export default function TeamMemberView({
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 🔼 Ende Policy-Pill */}
|
{/* 🔼 Ende Policy-Pill */}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export default function TeamPageClient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card maxWidth="auto">
|
<Card maxWidth="auto" bodyScrollable>
|
||||||
<TeamCardComponent
|
<TeamCardComponent
|
||||||
initialTeams={teams}
|
initialTeams={teams}
|
||||||
initialInvitationMap={invitationMap}
|
initialInvitationMap={invitationMap}
|
||||||
|
|||||||
@ -2,27 +2,26 @@
|
|||||||
import { NextResponse, type NextRequest } from 'next/server'
|
import { NextResponse, type NextRequest } from 'next/server'
|
||||||
import { prisma } from '@/lib/prisma'
|
import { prisma } from '@/lib/prisma'
|
||||||
import { getServerSession } from 'next-auth'
|
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 { sendServerSSEMessage } from '@/lib/sse-server-client'
|
||||||
import type { TeamJoinPolicy } from '@/types/team'
|
import type { TeamJoinPolicy } from '@/types/team'
|
||||||
|
|
||||||
export const runtime = 'nodejs' // ✅ Prisma-kompatibel
|
export const runtime = 'nodejs'
|
||||||
export const dynamic = 'force-dynamic' // (nur Vorsicht, POST ist eh dynamisch)
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
const ALLOWED = ['REQUEST', 'INVITE_ONLY'] as const
|
const ALLOWED = ['REQUEST', 'INVITE_ONLY'] as const
|
||||||
type AllowedPolicy = (typeof ALLOWED)[number]
|
type AllowedPolicy = (typeof ALLOWED)[number]
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
// ─ Session ─
|
// ⬇️ statt getServerSession(authOptions(req))
|
||||||
const session = await getServerSession(authOptions(req))
|
const session = await getServerSession(sessionAuthOptions)
|
||||||
|
|
||||||
const meId = session?.user?.steamId
|
const meId = session?.user?.steamId
|
||||||
if (!meId) {
|
if (!meId) {
|
||||||
return NextResponse.json({ message: 'Nicht eingeloggt' }, { status: 401 })
|
return NextResponse.json({ message: 'Nicht eingeloggt' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ Input ─
|
|
||||||
const body = await req.json().catch(() => ({} as any))
|
const body = await req.json().catch(() => ({} as any))
|
||||||
const teamId: string | undefined = body?.teamId
|
const teamId: string | undefined = body?.teamId
|
||||||
const joinPolicy: TeamJoinPolicy | undefined = body?.joinPolicy
|
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 })
|
return NextResponse.json({ message: 'Ungültige joinPolicy' }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ Daten ─
|
|
||||||
const [team, me] = await Promise.all([
|
const [team, me] = await Promise.all([
|
||||||
prisma.team.findUnique({
|
prisma.team.findUnique({
|
||||||
where: { id: teamId },
|
where: { id: teamId },
|
||||||
select: {
|
select: { id: true, leaderId: true, joinPolicy: true },
|
||||||
id: true,
|
|
||||||
leaderId: true,
|
|
||||||
joinPolicy: true,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
prisma.user.findUnique({
|
prisma.user.findUnique({
|
||||||
where: { steamId: meId },
|
where: { steamId: meId },
|
||||||
@ -70,8 +64,7 @@ export async function POST(req: NextRequest) {
|
|||||||
select: { id: true, joinPolicy: true },
|
select: { id: true, joinPolicy: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
// ─ SSE (nicht blockierend!) ─
|
// Fire-and-forget SSE
|
||||||
// Kein await → Request-Antwort wird NICHT aufgehalten
|
|
||||||
Promise.resolve().then(() =>
|
Promise.resolve().then(() =>
|
||||||
sendServerSSEMessage({
|
sendServerSSEMessage({
|
||||||
type: 'team-updated',
|
type: 'team-updated',
|
||||||
|
|||||||
@ -35,12 +35,12 @@ exports.Prisma = Prisma
|
|||||||
exports.$Enums = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.17.0
|
* Prisma Client JS version: 6.16.3
|
||||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.17.0",
|
client: "6.16.3",
|
||||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||||
@ -418,7 +418,7 @@ const config = {
|
|||||||
"value": "prisma-client-js"
|
"value": "prisma-client-js"
|
||||||
},
|
},
|
||||||
"output": {
|
"output": {
|
||||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||||
"fromEnvVar": null
|
"fromEnvVar": null
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@ -432,7 +432,7 @@ const config = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"previewFeatures": [],
|
"previewFeatures": [],
|
||||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||||
"isCustomOutput": true
|
"isCustomOutput": true
|
||||||
},
|
},
|
||||||
"relativeEnvPaths": {
|
"relativeEnvPaths": {
|
||||||
@ -440,8 +440,8 @@ const config = {
|
|||||||
"schemaEnvPath": "../../../.env"
|
"schemaEnvPath": "../../../.env"
|
||||||
},
|
},
|
||||||
"relativePath": "../../../prisma",
|
"relativePath": "../../../prisma",
|
||||||
"clientVersion": "6.17.0",
|
"clientVersion": "6.16.3",
|
||||||
"engineVersion": "c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
"engineVersion": "bb420e667c1820a8c05a38023385f6cc7ef8e83a",
|
||||||
"datasourceNames": [
|
"datasourceNames": [
|
||||||
"db"
|
"db"
|
||||||
],
|
],
|
||||||
|
|||||||
@ -20,12 +20,12 @@ exports.Prisma = Prisma
|
|||||||
exports.$Enums = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.17.0
|
* Prisma Client JS version: 6.16.3
|
||||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.17.0",
|
client: "6.16.3",
|
||||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = () => {
|
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
|
export import Exact = $Public.Exact
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.17.0
|
* Prisma Client JS version: 6.16.3
|
||||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||||
*/
|
*/
|
||||||
export type PrismaVersion = {
|
export type PrismaVersion = {
|
||||||
client: string
|
client: string
|
||||||
|
|||||||
@ -35,12 +35,12 @@ exports.Prisma = Prisma
|
|||||||
exports.$Enums = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.17.0
|
* Prisma Client JS version: 6.16.3
|
||||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.17.0",
|
client: "6.16.3",
|
||||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||||
@ -419,7 +419,7 @@ const config = {
|
|||||||
"value": "prisma-client-js"
|
"value": "prisma-client-js"
|
||||||
},
|
},
|
||||||
"output": {
|
"output": {
|
||||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||||
"fromEnvVar": null
|
"fromEnvVar": null
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@ -433,7 +433,7 @@ const config = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"previewFeatures": [],
|
"previewFeatures": [],
|
||||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||||
"isCustomOutput": true
|
"isCustomOutput": true
|
||||||
},
|
},
|
||||||
"relativeEnvPaths": {
|
"relativeEnvPaths": {
|
||||||
@ -441,8 +441,8 @@ const config = {
|
|||||||
"schemaEnvPath": "../../../.env"
|
"schemaEnvPath": "../../../.env"
|
||||||
},
|
},
|
||||||
"relativePath": "../../../prisma",
|
"relativePath": "../../../prisma",
|
||||||
"clientVersion": "6.17.0",
|
"clientVersion": "6.16.3",
|
||||||
"engineVersion": "c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
"engineVersion": "bb420e667c1820a8c05a38023385f6cc7ef8e83a",
|
||||||
"datasourceNames": [
|
"datasourceNames": [
|
||||||
"db"
|
"db"
|
||||||
],
|
],
|
||||||
|
|||||||
@ -151,7 +151,7 @@
|
|||||||
},
|
},
|
||||||
"./*": "./*"
|
"./*": "./*"
|
||||||
},
|
},
|
||||||
"version": "6.17.0",
|
"version": "6.16.3",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"imports": {
|
"imports": {
|
||||||
"#wasm-engine-loader": {
|
"#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: {
|
args: {
|
||||||
from: QueryPlanNode;
|
from: QueryPlanNode;
|
||||||
to: QueryPlanNode;
|
to: QueryPlanNode;
|
||||||
fields: string[];
|
|
||||||
};
|
};
|
||||||
} | {
|
} | {
|
||||||
type: 'initializeRecord';
|
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 = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.17.0
|
* Prisma Client JS version: 6.16.3
|
||||||
* Query Engine version: c0aafc03b8ef6cdced8654b9a817999e02457d6a
|
* Query Engine version: bb420e667c1820a8c05a38023385f6cc7ef8e83a
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.17.0",
|
client: "6.16.3",
|
||||||
engine: "c0aafc03b8ef6cdced8654b9a817999e02457d6a"
|
engine: "bb420e667c1820a8c05a38023385f6cc7ef8e83a"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||||
@ -418,7 +418,7 @@ const config = {
|
|||||||
"value": "prisma-client-js"
|
"value": "prisma-client-js"
|
||||||
},
|
},
|
||||||
"output": {
|
"output": {
|
||||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||||
"fromEnvVar": null
|
"fromEnvVar": null
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@ -432,7 +432,7 @@ const config = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"previewFeatures": [],
|
"previewFeatures": [],
|
||||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||||
"isCustomOutput": true
|
"isCustomOutput": true
|
||||||
},
|
},
|
||||||
"relativeEnvPaths": {
|
"relativeEnvPaths": {
|
||||||
@ -440,8 +440,8 @@ const config = {
|
|||||||
"schemaEnvPath": "../../../.env"
|
"schemaEnvPath": "../../../.env"
|
||||||
},
|
},
|
||||||
"relativePath": "../../../prisma",
|
"relativePath": "../../../prisma",
|
||||||
"clientVersion": "6.17.0",
|
"clientVersion": "6.16.3",
|
||||||
"engineVersion": "c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
"engineVersion": "bb420e667c1820a8c05a38023385f6cc7ef8e83a",
|
||||||
"datasourceNames": [
|
"datasourceNames": [
|
||||||
"db"
|
"db"
|
||||||
],
|
],
|
||||||
|
|||||||
@ -21,6 +21,7 @@ function guessTzFromCountry(cc?: string | null): string | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 👉 Login-/Auth-Factory mit echtem Request (für /api/auth/... Routen, SignIn etc.)
|
||||||
export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
providers: [
|
providers: [
|
||||||
@ -32,7 +33,6 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
|||||||
const steamProfile = profile as SteamProfile
|
const steamProfile = profile as SteamProfile
|
||||||
const location = steamProfile.loccountrycode ?? null
|
const location = steamProfile.loccountrycode ?? null
|
||||||
|
|
||||||
// create/update User (wie gehabt)
|
|
||||||
const existing = await prisma.user.findUnique({
|
const existing = await prisma.user.findUnique({
|
||||||
where: { steamId: steamProfile.steamid },
|
where: { steamId: steamProfile.steamid },
|
||||||
select: { timeZone: true },
|
select: { timeZone: true },
|
||||||
@ -68,7 +68,6 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
|||||||
token.image = steamProfile.avatarfull
|
token.image = steamProfile.avatarfull
|
||||||
}
|
}
|
||||||
|
|
||||||
// DB laden & Flags
|
|
||||||
const userInDb = await prisma.user.findUnique({
|
const userInDb = await prisma.user.findUnique({
|
||||||
where: { steamId: token.steamId || token.sub || '' },
|
where: { steamId: token.steamId || token.sub || '' },
|
||||||
select: { teamId: true, isAdmin: true, steamId: true, timeZone: true },
|
select: { teamId: true, isAdmin: true, steamId: true, timeZone: true },
|
||||||
@ -82,7 +81,6 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
|||||||
token.timeZone = undefined
|
token.timeZone = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🎯 Faceit-Sync ausgelagert
|
|
||||||
if (token.steamId) {
|
if (token.steamId) {
|
||||||
await syncFaceitProfile(prisma, token.steamId)
|
await syncFaceitProfile(prisma, token.steamId)
|
||||||
}
|
}
|
||||||
@ -97,10 +95,12 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
|||||||
steamId: token.steamId,
|
steamId: token.steamId,
|
||||||
name: token.name,
|
name: token.name,
|
||||||
image: token.image,
|
image: token.image,
|
||||||
team: token.team ?? null,
|
team: (token as any).team ?? null,
|
||||||
isAdmin: token.isAdmin ?? false,
|
isAdmin: (token as any).isAdmin ?? false,
|
||||||
timeZone: (token as any).timeZone ?? null,
|
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
|
return session
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -112,7 +112,31 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
|||||||
return url.startsWith(baseUrl) ? url : baseUrl;
|
return url.startsWith(baseUrl) ? url : baseUrl;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
session: { strategy: 'jwt' },
|
||||||
})
|
})
|
||||||
|
|
||||||
// ➕ Base config
|
// ⚠️ NEU: Minimal-Options NUR für getServerSession() in App-Routen/Server Actions
|
||||||
export const baseAuthOptions: NextAuthOptions = authOptions({} as NextRequest)
|
// → 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 {NextResponse} from 'next/server';
|
||||||
import type {NextRequest} from 'next/server';
|
import type {NextRequest} from 'next/server';
|
||||||
import createIntlMiddleware from 'next-intl/middleware';
|
import createIntlMiddleware from 'next-intl/middleware';
|
||||||
import {getToken} from 'next-auth/jwt';
|
import {getToken} from 'next-auth/jwt';
|
||||||
import {routing} from './i18n/routing';
|
import {routing} from './i18n/routing';
|
||||||
|
|
||||||
// 1) i18n-Middleware vorbereiten
|
// 1) i18n-Middleware vorbereiten (aber NICHT für /api etc. benutzen)
|
||||||
const handleI18n = createIntlMiddleware(routing);
|
const handleI18n = createIntlMiddleware(routing);
|
||||||
|
|
||||||
// Helpers
|
// Helpers (deine bleiben unverändert)
|
||||||
function getCurrentLocaleFromPath(pathname: string, locales: readonly string[], fallback: string) {
|
function getCurrentLocaleFromPath(pathname: string, locales: readonly string[], fallback: string) {
|
||||||
const first = pathname.split('/')[1];
|
const first = pathname.split('/')[1];
|
||||||
return locales.includes(first) ? first : fallback;
|
return locales.includes(first) ? first : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripLeadingLocale(pathname: string, locales: readonly string[]) {
|
function stripLeadingLocale(pathname: string, locales: readonly string[]) {
|
||||||
const parts = pathname.split('/');
|
const parts = pathname.split('/');
|
||||||
const first = parts[1];
|
const first = parts[1];
|
||||||
@ -22,33 +22,44 @@ function stripLeadingLocale(pathname: string, locales: readonly string[]) {
|
|||||||
}
|
}
|
||||||
return pathname;
|
return pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProtectedPath(pathnameNoLocale: string) {
|
function isProtectedPath(pathnameNoLocale: string) {
|
||||||
return (
|
return (
|
||||||
pathnameNoLocale.startsWith('/dashboard') ||
|
pathnameNoLocale.startsWith('/dashboard') ||
|
||||||
pathnameNoLocale.startsWith('/settings') ||
|
pathnameNoLocale.startsWith('/settings') ||
|
||||||
pathnameNoLocale.startsWith('/matches') ||
|
pathnameNoLocale.startsWith('/matches') ||
|
||||||
pathnameNoLocale.startsWith('/team') || // ← hinzugefügt
|
pathnameNoLocale.startsWith('/team') ||
|
||||||
pathnameNoLocale.startsWith('/admin')
|
pathnameNoLocale.startsWith('/admin')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function middleware(req: NextRequest) {
|
export default async function middleware(req: NextRequest) {
|
||||||
// 2) Erst i18n arbeiten lassen
|
const { pathname } = req.nextUrl;
|
||||||
const i18nRes = handleI18n(req);
|
|
||||||
|
|
||||||
// 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')) {
|
if (i18nRes.headers.get('location') || i18nRes.headers.get('x-middleware-rewrite')) {
|
||||||
return i18nRes;
|
return i18nRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ 2) Ab hier deine Auth-Logik nur für geschützte Seiten
|
||||||
const {locales, defaultLocale} = routing;
|
const {locales, defaultLocale} = routing;
|
||||||
const url = req.nextUrl;
|
const url = req.nextUrl;
|
||||||
const pathname = url.pathname;
|
|
||||||
const currentLocale = getCurrentLocaleFromPath(pathname, locales, defaultLocale);
|
|
||||||
const pathnameNoLocale = stripLeadingLocale(pathname, locales);
|
const pathnameNoLocale = stripLeadingLocale(pathname, locales);
|
||||||
|
|
||||||
// 3) Nur für geschützte Pfade Auth prüfen
|
|
||||||
if (!isProtectedPath(pathnameNoLocale)) {
|
if (!isProtectedPath(pathnameNoLocale)) {
|
||||||
return i18nRes;
|
return i18nRes;
|
||||||
}
|
}
|
||||||
@ -58,6 +69,7 @@ export default async function middleware(req: NextRequest) {
|
|||||||
// Adminschutz
|
// Adminschutz
|
||||||
if (pathnameNoLocale.startsWith('/admin')) {
|
if (pathnameNoLocale.startsWith('/admin')) {
|
||||||
if (!token || !(token as any).isAdmin) {
|
if (!token || !(token as any).isAdmin) {
|
||||||
|
const currentLocale = getCurrentLocaleFromPath(pathname, locales, defaultLocale);
|
||||||
const redirectUrl = url.clone();
|
const redirectUrl = url.clone();
|
||||||
redirectUrl.pathname = `/${currentLocale}/dashboard`;
|
redirectUrl.pathname = `/${currentLocale}/dashboard`;
|
||||||
return NextResponse.redirect(redirectUrl);
|
return NextResponse.redirect(redirectUrl);
|
||||||
@ -67,15 +79,16 @@ export default async function middleware(req: NextRequest) {
|
|||||||
// Allgemeiner Auth-Schutz
|
// Allgemeiner Auth-Schutz
|
||||||
if (!token) {
|
if (!token) {
|
||||||
const loginUrl = new URL('/api/auth/signin', req.url);
|
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);
|
return NextResponse.redirect(loginUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alles gut → weiter
|
|
||||||
return i18nRes;
|
return i18nRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matcher robuster halten (gut so – ich würde assets & favicon ergänzen)
|
||||||
export const config = {
|
export const config = {
|
||||||
// Standard: alles außer /api, _next, statische Dateien
|
matcher: [
|
||||||
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
|
'/((?!api|trpc|_next|_vercel|assets|favicon.ico|robots.txt|sitemap.xml|.*\\..*).*)'
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -38,4 +38,6 @@ export type Team = {
|
|||||||
inactivePlayers: Player[]
|
inactivePlayers: Player[]
|
||||||
invitedPlayers: InvitedPlayer[]
|
invitedPlayers: InvitedPlayer[]
|
||||||
players?: MatchPlayer[]
|
players?: MatchPlayer[]
|
||||||
|
logoUpdatedAt?: string | Date | null
|
||||||
|
updatedAt?: string | Date | null
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user