diff --git a/package-lock.json b/package-lock.json index 8abe5076..222ed89e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -227,7 +228,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -237,7 +238,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -271,7 +272,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -362,7 +363,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -714,7 +715,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -1720,9 +1722,9 @@ } }, "node_modules/@fastify/swagger": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.6.1.tgz", - "integrity": "sha512-fKlpJqFMWoi4H3EdUkDaMteEYRCfQMEkK0HJJ0eaf4aRlKd8cbq0pVkOfXDXmtvMTXYcnx3E+l023eFDBsA1HA==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.7.0.tgz", + "integrity": "sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==", "funding": [ { "type": "github", @@ -1734,6 +1736,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "fastify-plugin": "^5.0.0", "json-schema-resolver": "^3.0.0", @@ -1803,9 +1806,9 @@ } }, "node_modules/@fastify/swagger-ui/node_modules/glob": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", - "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.2.tgz", + "integrity": "sha512-035InabNu/c1lW0tzPhAgapKctblppqsKKG9ZaNzbr+gXwWMjXoiyGSyB9sArzrjG7jY+zntRq5ZSUYemrnWVQ==", "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.1.2", @@ -2705,9 +2708,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", "dev": true, "license": "MIT" }, @@ -3074,13 +3077,13 @@ } }, "node_modules/@scalar/fastify-api-reference": { - "version": "1.44.14", - "resolved": "https://registry.npmjs.org/@scalar/fastify-api-reference/-/fastify-api-reference-1.44.14.tgz", - "integrity": "sha512-1Gmfh7z9hcvC7dhq4jhIruaaSGZDSdEBk6OLO6LpQklNpzi0PQGBVDFetEbvA4fPLcMfw/j8sXv9lTnVfUc5+w==", + "version": "1.44.16", + "resolved": "https://registry.npmjs.org/@scalar/fastify-api-reference/-/fastify-api-reference-1.44.16.tgz", + "integrity": "sha512-UwKWu4WV6r6ORB/TfcjKLJchWSi+OPgjcK0pXm6gdNhbJuZrsCuzbtPO7mfMYj96mw5IWjLZfatCu/lIHmKoUA==", "license": "MIT", "dependencies": { "@scalar/core": "0.3.37", - "@scalar/openapi-parser": "0.24.8", + "@scalar/openapi-parser": "0.24.9", "@scalar/openapi-types": "0.5.3", "fastify-plugin": "^4.5.1", "github-slugger": "^2.0.0" @@ -3105,9 +3108,9 @@ } }, "node_modules/@scalar/json-magic": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.10.0.tgz", - "integrity": "sha512-7QCaKjhPCNBe5vieI0zZcxOupI+kk2PoWL/tuxB7ROLbDOIdmeZ8x1FrZqdtamDPi+zsQGx+kc/cvTDz7GdTKw==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.11.0.tgz", + "integrity": "sha512-1zBseDDEPkKlAVd9lT1HlK9Nefeh0YEE+pcmyDL3J5derIZn9UYXAFecdkeXMdjDtWDgcrkmWCrHhpoT7zVKdQ==", "license": "MIT", "dependencies": { "@scalar/helpers": "0.2.11", @@ -3118,13 +3121,13 @@ } }, "node_modules/@scalar/openapi-parser": { - "version": "0.24.8", - "resolved": "https://registry.npmjs.org/@scalar/openapi-parser/-/openapi-parser-0.24.8.tgz", - "integrity": "sha512-1ZFC/nNbH/e/rriDgfsXCcr23WphvqHVxX8V90qhR8c9QWXNb0cyNXl3hNjvtTB7Lg7O9aS22BUZ14gabCWvRQ==", + "version": "0.24.9", + "resolved": "https://registry.npmjs.org/@scalar/openapi-parser/-/openapi-parser-0.24.9.tgz", + "integrity": "sha512-uqpwt6ZQJQu4c3CvMsJiXMUj32113yrclsDC31hlL33vEUS5JU9dCYfY27oLSCVoKl8R8KihlnEcbfRnH/O/GA==", "license": "MIT", "dependencies": { "@scalar/helpers": "0.2.11", - "@scalar/json-magic": "0.10.0", + "@scalar/json-magic": "0.11.0", "@scalar/openapi-types": "0.5.3", "@scalar/openapi-upgrader": "0.1.8", "ajv": "^8.17.1", @@ -3735,10 +3738,11 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.11.tgz", - "integrity": "sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ==", + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3759,6 +3763,7 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3831,17 +3836,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -3854,7 +3859,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -3870,16 +3875,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "engines": { @@ -3895,14 +3901,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "engines": { @@ -3917,14 +3923,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3935,9 +3941,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", "dev": true, "license": "MIT", "engines": { @@ -3952,15 +3958,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -3977,9 +3983,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "dev": true, "license": "MIT", "engines": { @@ -3991,16 +3997,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -4045,16 +4051,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4069,13 +4075,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4087,16 +4093,16 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", - "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.2", + "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, @@ -4457,6 +4463,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4510,6 +4517,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4944,13 +4952,13 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -5113,6 +5121,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5708,6 +5717,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -6557,6 +6567,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7190,6 +7201,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", @@ -7536,13 +7548,13 @@ } }, "node_modules/framer-motion": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.33.0.tgz", - "integrity": "sha512-ca8d+rRPcDP5iIF+MoT3WNc0KHJMjIyFAbtVLvM9eA7joGSpeqDfiNH/kCs1t4CHi04njYvWyj0jS4QlEK/rJQ==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.0.tgz", + "integrity": "sha512-+/H49owhzkzQyxtn7nZeF4kdH++I2FWrESQ184Zbcw5cEqNHYkE5yxWxcTLSj5lNx3NWdbIRy5FHqUvetD8FWg==", "dev": true, "license": "MIT", "dependencies": { - "motion-dom": "^12.33.0", + "motion-dom": "^12.34.0", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, @@ -8064,11 +8076,12 @@ } }, "node_modules/hono": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.8.tgz", - "integrity": "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==", + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -8150,6 +8163,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -8854,9 +8868,9 @@ } }, "node_modules/jackspeak": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.1.tgz", - "integrity": "sha512-GPBXyfcZSGujjddPeA+V34bW70ZJT7jzCEbloVasSH4yjiqWqXHX8iZQtZdVbOhc5esSeAIuiSmMutRZQB/olg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^9.0.0" @@ -9625,8 +9639,9 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "devOptional": true, + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", @@ -9832,9 +9847,9 @@ } }, "node_modules/motion-dom": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.33.0.tgz", - "integrity": "sha512-XRPebVypsl0UM+7v0Hr8o9UAj0S2djsQWRdHBd5iVouVpMrQqAI0C/rDAT3QaYnXnHuC5hMcwDHCboNeyYjPoQ==", + "version": "12.34.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.0.tgz", + "integrity": "sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10252,7 +10267,8 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/optionator": { "version": "0.9.4", @@ -10416,9 +10432,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -10471,9 +10487,9 @@ } }, "node_modules/pino": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", - "integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", "license": "MIT", "dependencies": { "@pinojs/redact": "^0.4.0", @@ -10593,6 +10609,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10683,6 +10700,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10710,6 +10728,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.3.0", "@prisma/dev": "0.20.0", @@ -11093,6 +11112,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11102,6 +11122,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11997,9 +12018,9 @@ } }, "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" @@ -12019,7 +12040,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12794,9 +12815,9 @@ } }, "node_modules/type-fest": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.3.tgz", - "integrity": "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -12888,6 +12909,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12897,16 +12919,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -13055,6 +13077,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -13226,9 +13249,9 @@ } }, "node_modules/webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "version": "5.105.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.1.tgz", + "integrity": "sha512-Gdj3X74CLJJ8zy4URmK42W7wTZUJrqL+z8nyGEr4dTN0kb3nVs+ZvjbTOqRYPD7qX4tUmwyHL9Q9K6T1seW6Yw==", "dev": true, "license": "MIT", "dependencies": { @@ -13686,6 +13709,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -13862,9 +13886,9 @@ } }, "srcs/game/node_modules/@types/node": { - "version": "20.19.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", - "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { @@ -13975,9 +13999,9 @@ } }, "srcs/nginx/node_modules/@types/node": { - "version": "20.19.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", - "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/srcs/nginx/src/App.tsx b/srcs/nginx/src/App.tsx index cadd17cc..dfb14392 100644 --- a/srcs/nginx/src/App.tsx +++ b/srcs/nginx/src/App.tsx @@ -3,20 +3,33 @@ import { ProfilePage } from './pages/ProfilePage'; import { LoginPage } from './pages/LoginRegisterPage'; import { useAuth } from './providers/AuthProvider'; import { AnimationPage } from './pages/AnimationPage'; +import TournamentRoutes from './router/TournamentRoutes'; const GuestRoute = ({ children }: { children: React.ReactNode }) => { - const { user, isLoggedIn } = useAuth(); - if (user && isLoggedIn) { + const { user, isLoggedIn, isAuthChecked } = useAuth(); + + if (!isAuthChecked) { + return null; // ou loader + } + + if (isLoggedIn && user?.username) { return ; } + return children; }; - const MeRedirect = () => { - const { user } = useAuth(); - // if (isLoading) return
Loading ...
; - if (!user) return ; - return ; + const { user, isAuthChecked } = useAuth(); + + if (!isAuthChecked) { + return null; // ou loader + } + + if (!user || !user.username) { + return ; + } + + return ; }; export const App = () => { @@ -42,6 +55,7 @@ export const App = () => { /> }> }> + } /> ); diff --git a/srcs/nginx/src/api/auth-api.ts b/srcs/nginx/src/api/auth-api.ts index 73073857..b26c92d9 100644 --- a/srcs/nginx/src/api/auth-api.ts +++ b/srcs/nginx/src/api/auth-api.ts @@ -54,8 +54,23 @@ export const authApi = { return data?.user?.username; }, + // me: async (): Promise => { + // usernameSchema.parse(username); + // // const response = await api.get(`/auth/me/`); + // const response = { + // data: { + // authId: 1, + // email: 'toto@mail.com', + // username: 'Toto', + // }, + // message: 'OK', + // }; + // return response.data; + // }, me: async (): Promise => { - const response = await api.get(`/auth/me/`); - return response.data; + const { data } = await api.get('/auth/me', { + withCredentials: true, + }); + return data; }, }; diff --git a/srcs/nginx/src/components/atoms/BracketLines.tsx b/srcs/nginx/src/components/atoms/BracketLines.tsx new file mode 100644 index 00000000..27e627b9 --- /dev/null +++ b/srcs/nginx/src/components/atoms/BracketLines.tsx @@ -0,0 +1,99 @@ +import { RefObject, useEffect, useState } from 'react'; + +export interface BracketConnection { + from: RefObject; + to: RefObject; +} + +interface Point { + x: number; + y: number; +} + +interface ComputedLine { + from: Point; + to: Point; +} + +interface BracketLinesProps { + // coordinate reference + containerRef: RefObject; + connections: BracketConnection[]; +} + +/* renvoi les coordonnées du centre de l'objet / div + * car getBoundingClientRect renvoie les coordonnées viewport du rectangle + * cette fontion utilitaire permet aux lignes de partir du centre des capsules + */ +function centerOf(rect: DOMRect): Point { + return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; +} + +/* BracketLines est un composant de rendu SVG qui dessine dynamiquement des lignes + * entre des éléments du DOM, en restant synchronisé avec le layout réel + * (responsive, scroll, resize). + */ +export function BracketLines({ containerRef, connections }: BracketLinesProps) { + const [lines, setLines] = useState([]); + + useEffect(() => { + const compute = () => { + const container = containerRef.current; + if (!container) return; + // on récupère les coordonnées du container + const cRect = container.getBoundingClientRect(); + + const computed: ComputedLine[] = []; + + for (const { from, to } of connections) { + const aEl = from.current; + const bEl = to.current; + if (!aEl || !bEl) continue; + + const a = centerOf(aEl.getBoundingClientRect()); + const b = centerOf(bEl.getBoundingClientRect()); + + //convert viewport -> container local coords + computed.push({ + from: { x: a.x - cRect.left, y: a.y - cRect.top }, + to: { x: b.x - cRect.left, y: b.y - cRect.top }, + }); + } + + setLines(computed); + }; + + /*recalcul : au montage, au resize, au scroll (y compris scrolls internes)*/ + compute(); + + window.addEventListener('resize', compute); + window.addEventListener('scroll', compute, true); // capte scroll de conteneurs + // évite les fuite de mémoire + return () => { + window.removeEventListener('resize', compute); + window.removeEventListener('scroll', compute, true); + }; + }, [containerRef, connections]); + + return ( + + {lines.map((line, i) => { + // courbe de bézier cubique + const cx = (line.from.x + line.to.x) / 2; + + return ( + + ); + })} + + ); +} diff --git a/srcs/nginx/src/components/atoms/CircleButton.tsx b/srcs/nginx/src/components/atoms/CircleButton.tsx new file mode 100644 index 00000000..f82d29a1 --- /dev/null +++ b/srcs/nginx/src/components/atoms/CircleButton.tsx @@ -0,0 +1,31 @@ +import { motion } from 'framer-motion'; +import React from 'react'; + +interface CircleButtonProps extends React.ButtonHTMLAttributes { + children?: React.ReactNode; +} + +const dropdownStyle = 'shadow-[0_10px_10px_1px_rgba(205,205,205,0.4)] '; + +export const CircleButton = ({ children, className = '', ...props }: CircleButtonProps) => ( + +); diff --git a/srcs/nginx/src/components/atoms/MatchNode.tsx b/srcs/nginx/src/components/atoms/MatchNode.tsx new file mode 100644 index 00000000..67853c90 --- /dev/null +++ b/srcs/nginx/src/components/atoms/MatchNode.tsx @@ -0,0 +1,46 @@ +import { MatchStatus } from '../../types/types'; +import { useTranslation } from 'react-i18next'; + +interface MatchNodeProps { + label: string; + status: MatchStatus; + highlight?: boolean; + onStart?: () => void; +} + +export function MatchNode({ label, status, highlight = false, onStart }: MatchNodeProps) { + const canStart = status === 'ready'; + const { t } = useTranslation(); + + return ( +
+ {/* Label */} + {label} + + {/* Start button */} + +
+ ); +} diff --git a/srcs/nginx/src/components/atoms/NavDropDown.tsx b/srcs/nginx/src/components/atoms/NavDropDown.tsx index 9d6814e3..93bceea0 100644 --- a/srcs/nginx/src/components/atoms/NavDropDown.tsx +++ b/srcs/nginx/src/components/atoms/NavDropDown.tsx @@ -21,11 +21,10 @@ export const NavDropdown = ({ isOpen, children, yTranslate = 0 }: NavDropdownPro absolute top-full left-1/2 mt-5 w-64 pt-6 pb-10 px-4 bg-slate-100/80 backdrop-blur-xl - border-t-0 border-b-0 border-x-0 rounded-t-none rounded-b-[8rem] flex flex-col items-center justify-start - z-50 ${dropdownStyle} + z-15 ${dropdownStyle} `} >
diff --git a/srcs/nginx/src/components/atoms/PlayerCapsule.tsx b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx new file mode 100644 index 00000000..86d00a94 --- /dev/null +++ b/srcs/nginx/src/components/atoms/PlayerCapsule.tsx @@ -0,0 +1,34 @@ +import { Player } from '../../types/types'; +import Avatar from './Avatar'; + +export function PlayerCapsule({ player }: { player: Player }) { + const isOnline = player.online === true; + const isWaiting = player.status === 'waiting'; + return ( +
+ {/* Avatar + status */} +
+ + + +
+ + {/* Name */} + + {player.name} + +
+ ); +} diff --git a/srcs/nginx/src/components/atoms/TournamentList.tsx b/srcs/nginx/src/components/atoms/TournamentList.tsx new file mode 100644 index 00000000..66e114ef --- /dev/null +++ b/srcs/nginx/src/components/atoms/TournamentList.tsx @@ -0,0 +1,137 @@ +import { useTranslation } from 'react-i18next'; + +export type Tournament = { + id: string; + name: string; + players: number; + maxPlayers: number; + status: 'WAITING' | 'IN_PROGRESS' | 'FINISHED'; + createdAt: string; +}; + +type tournamentsProps = { + tournaments: Tournament[]; + onJoin: (id: string) => void; +}; + +const statusLabel: Record = { + WAITING: 'game.waiting', + IN_PROGRESS: 'game.in_progress', + FINISHED: 'finished', +}; + +const statusColor: Record = { + WAITING: 'text-emerald-600', + IN_PROGRESS: 'text-amber-600', + FINISHED: 'text-gray-500', +}; + +/* Version for computer screen + * with a html table + */ +export function TournamentTableDesktop({ tournaments, onJoin }: tournamentsProps) { + const { t } = useTranslation(); + return ( +
+
+

+ {t('game.tournament_available')} +

+ +
+ + + + + + + + + + + + {tournaments.map((tour) => ( + + + + + + + + + + ))} + + {tournaments.length === 0 && ( + + + + )} + +
{t('game.name')}{t('game.players')}{t('game.status')}{t('game.action')}
{tour.name} + {tour.players} / {tour.maxPlayers} + + {t(statusLabel[tour.status])} + + {tour.status === 'WAITING' ? ( + + ) : ( + {t('game.unavailable')} + )} +
+ {t('game.no_tournament')} +
+
+
+
+ ); +} +/* Version for mobile because table can't have a good small Version + * here i use card + * */ +export function TournamentListMobile({ tournaments, onJoin }: tournamentsProps) { + const { t } = useTranslation(); + return ( + <> + {tournaments.map((tour) => ( +
+
+ {tour.name} + + {tour.players} / {tour.maxPlayers} + +
+ +
+ + {tour.status === 'WAITING' ? t('game.waiting') : t('game.in_progress')} + + + {tour.status === 'WAITING' ? ( + + ) : ( + {t('game.unavailable')} + )} +
+
+ ))} + + ); +} diff --git a/srcs/nginx/src/components/molecules/FriendsList.tsx b/srcs/nginx/src/components/molecules/FriendsList.tsx new file mode 100644 index 00000000..98ac660f --- /dev/null +++ b/srcs/nginx/src/components/molecules/FriendsList.tsx @@ -0,0 +1,62 @@ +import { useState } from 'react'; +import { Player } from 'src/types/types'; +import { PlayerCapsule } from '../atoms/PlayerCapsule'; +import { useTranslation } from 'react-i18next'; + +interface FriendsListProps { + friends: Player[]; +} + +export const FriendsList = ({ friends }: FriendsListProps) => { + const [open, setOpen] = useState(false); + const { t } = useTranslation(); + + return ( +
+
+ {/* Header / Toggle */} + + + {/* List */} + {open && ( +
+ {friends.map((friend) => ( + + ))} +
+ )} +
+
+ ); +}; diff --git a/srcs/nginx/src/components/molecules/NavBar.tsx b/srcs/nginx/src/components/molecules/NavBar.tsx index 390c8528..c4995546 100644 --- a/srcs/nginx/src/components/molecules/NavBar.tsx +++ b/srcs/nginx/src/components/molecules/NavBar.tsx @@ -20,7 +20,7 @@ export const NavBar = () => { const playItems = [ { label: t('navbar.play_friend'), to: '/friends' }, { label: t('navbar.play_ai'), to: '/ai' }, - { label: t('navbar.play_tournament'), to: '/tournament' }, + { label: t('navbar.play_tournament'), to: '/tournaments' }, ]; const statsItems = [ diff --git a/srcs/nginx/src/components/molecules/TournamentBracket.tsx b/srcs/nginx/src/components/molecules/TournamentBracket.tsx new file mode 100644 index 00000000..1cb362fe --- /dev/null +++ b/srcs/nginx/src/components/molecules/TournamentBracket.tsx @@ -0,0 +1,98 @@ +import { Player } from '../../types/types'; +import { BracketConnection, BracketLines } from '../atoms/BracketLines'; +import { PlayerCapsule } from '../atoms/PlayerCapsule'; +import { MatchNode } from '../atoms/MatchNode'; +import { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; + +interface TournamentBracketProps { + players: [Player, Player, Player, Player]; +} + +export function TournamentBracket({ players }: TournamentBracketProps) { + const { t } = useTranslation(); + const [p1, p2, p3, p4] = players; + const containerRef = useRef(null); + const p1Ref = useRef(null); + const p2Ref = useRef(null); + const p3Ref = useRef(null); + const p4Ref = useRef(null); + + const semiLeftRef = useRef(null); + const semiRightRef = useRef(null); + const finalRef = useRef(null); + const littleFinalRef = useRef(null); + + const connections: BracketConnection[] = [ + { from: p1Ref, to: semiLeftRef }, + { from: p2Ref, to: semiLeftRef }, + { from: p3Ref, to: semiRightRef }, + { from: p4Ref, to: semiRightRef }, + { from: semiLeftRef, to: finalRef }, + { from: littleFinalRef, to: finalRef }, + { from: semiRightRef, to: littleFinalRef }, + ]; + return ( +
+ + +
+ {/* LEFT */} +
+
+ +
+
+ +
+
+ + {/* CENTER */} +
+
+ console.log(`startSemiFinal('left')`)} + /> +
+ +
+ console.log(`startFinal()`)} + /> +
+
+ console.log(`startLittleFinal()`)} + /> +
+ +
+ console.log(`startSemiFinal('right')`)} + /> +
+
+ + {/* RIGHT */} +
+
+ +
+
+ +
+
+
+
+ ); +} diff --git a/srcs/nginx/src/components/organisms/PageContainer.tsx b/srcs/nginx/src/components/organisms/PageContainer.tsx index fabe8efa..cbda734a 100644 --- a/srcs/nginx/src/components/organisms/PageContainer.tsx +++ b/srcs/nginx/src/components/organisms/PageContainer.tsx @@ -28,7 +28,7 @@ export const Page = ({ children, title, className }: PageProps) => {
} - {title &&

{title}

} + {title &&

{title}

} {children}
diff --git a/srcs/nginx/src/components/organisms/TournamentLayout.tsx b/srcs/nginx/src/components/organisms/TournamentLayout.tsx new file mode 100644 index 00000000..7bcca19b --- /dev/null +++ b/srcs/nginx/src/components/organisms/TournamentLayout.tsx @@ -0,0 +1,41 @@ +import { Outlet } from 'react-router-dom'; +import Background from '../atoms/Background'; +import { NavBar } from '../molecules/NavBar'; +import { Player } from '../../types/types'; +import { FriendsList } from '../molecules/FriendsList'; + +const colors = { + start: '#00ff9f', + end: '#0088ff', +}; + +/*This component is the architecture of all tournament pages.*/ +export default function TournamentLayout() { + const MOCK_PLAYERS: [Player, Player, Player, Player] = [ + { id: '1', name: 'johnny', avatar: null, online: true, status: 'connected' }, + { id: '2', name: 'eddy', avatar: null, online: false, status: 'connected' }, + { id: '3', name: 'khaled', avatar: null, online: true, status: 'connected' }, + { id: '4', name: 'danny', avatar: null, online: false, status: 'connected' }, + ] as const; + return ( +
+ + { +
+ +
+ } +
+ {MOCK_PLAYERS.length > 0 && } + + +
+
+
+ ); +} diff --git a/srcs/nginx/src/locales/en/common.json b/srcs/nginx/src/locales/en/common.json index e5f2d079..be3207ed 100644 --- a/srcs/nginx/src/locales/en/common.json +++ b/srcs/nginx/src/locales/en/common.json @@ -66,5 +66,29 @@ "copyright": "© 2026 . All rights reserved.", "privacyPolicy": "Privacy Policy", "termsOfService": "Terms of Service" + }, + "game": { + "tournament": "Tournament", + "game": "Game", + "participate": "Participate", + "create": "Create", + "tournament_available": "Tournament available", + "name": "Pseudo", + "players": "Nb players", + "status": "Status", + "action": "Action", + "join": "Join", + "unavailable": "Unavailable", + "waiting": "Waiting", + "in_progress": "In progress", + "finished": "Finished", + "no_tournament": "No tournament", + "final": "Final", + "semi_final": "Semi-Final", + "little_final": "Little-Finale", + "start": "Start", + "friends": "Friends", + "waitiing": "Waiting...", + "creating": "Creating tournament..." } } diff --git a/srcs/nginx/src/locales/fr/common.json b/srcs/nginx/src/locales/fr/common.json index c2f69834..00879694 100644 --- a/srcs/nginx/src/locales/fr/common.json +++ b/srcs/nginx/src/locales/fr/common.json @@ -66,5 +66,29 @@ "copyright": "© 2026 Tous droits réservés.", "privacyPolicy": "Politique de confidentialité", "termsOfService": "Conditions d'utilisation" + }, + "game": { + "tournament": "Tournoi", + "game": "Jeu", + "participate": "Participer", + "create": "Créer", + "tournament_available": "Tournois disponibles", + "name": "Pseudo", + "players": "Nb joueurs", + "status": "Statut", + "action": "Action", + "join": "Rejoindre", + "unavailable": "Indisponible", + "waiting": "En attente", + "in_progress": "En cours", + "finished": "Terminé", + "no_tournament": "Aucun tournoi disponible", + "final": "Finale", + "semi_final": "Demi-Finale", + "little_final": "Petite-Finale", + "start": "Jouer", + "friends": "Amis", + "waitiing": "Attente...", + "creating": "Création du tournoi..." } } diff --git a/srcs/nginx/src/locales/tf/common.json b/srcs/nginx/src/locales/tf/common.json index ff94361f..90cf9465 100644 --- a/srcs/nginx/src/locales/tf/common.json +++ b/srcs/nginx/src/locales/tf/common.json @@ -66,5 +66,29 @@ "copyright": "© 2026 – C'est à nous.", "privacyPolicy": "La loi du milieu (on balance rien, t'es en sécurité)", "termsOfService": "Les règles du jeu (lis ça, sinon t’es un rigolo)" + }, + "game": { + "tournament": "Vendetta", + "game": "La mort ou la vie", + "participate": "Tu cherches des embrouilles!", + "create": "Je vous mets au défi!", + "tournament_available": "Vendetta disponibles", + "name": "Blaz", + "players": "Nb truands", + "status": "Statut", + "action": "Action", + "join": "Rejoindre", + "unavailable": "Indisponible", + "waiting": "En attente", + "in_progress": "Contrat en cours", + "finished": "Terminé", + "no_tournament": "Aucune vendetta disponible", + "final": "Combat final", + "semi_final": "On prends du Galon", + "little_final": "Les loosers c'est ici", + "start": "C'est parti", + "friends": "Les potes", + "waitiing": "On t'attend...", + "creating": "Création de la vendetta..." } } diff --git a/srcs/nginx/src/pages/TournamentCreatePage.tsx b/srcs/nginx/src/pages/TournamentCreatePage.tsx new file mode 100644 index 00000000..351b52ad --- /dev/null +++ b/srcs/nginx/src/pages/TournamentCreatePage.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + +/* This component displays a loader until the tournament is created by the backend. + * Use `useEffect` is used to redirect the user. + */ +export default function TournamentCreatePage() { + const navigate = useNavigate(); + const [error, setError] = useState(null); + const { t } = useTranslation(); + + useEffect(() => { + async function createTournament() { + try { + // A remplacer par le vrai appel API + const res = await fakeCreateTournament(); + + navigate(`/tournaments/${res.id}`); + } catch (err) { + setError('Failed to create tournament'); + } + } + + createTournament(); + }, [navigate]); + + return ( +
+ {!error ? ( + <> + +

{t('game.creating')}

+ + ) : ( +
{error}
+ )} +
+ ); +} + +/* + * Fake API simulation + * This function should be replaced by a proper call to the backend. + */ + +async function fakeCreateTournament() { + return new Promise<{ id: string }>((resolve) => { + setTimeout(() => { + resolve({ id: '42' }); + }, 1500); + }); +} + +/* Tailwindcss allows you to create animations very quickly and easily + */ +function SpinLoader() { + return ( +
+ {/* Outer animated ring */} +
+ + {/* Inner pulse */} +
+ + {/* Glow effect */} +
+
+ ); +} diff --git a/srcs/nginx/src/pages/TournamentMenuPage.tsx b/srcs/nginx/src/pages/TournamentMenuPage.tsx new file mode 100644 index 00000000..aa7bc0fc --- /dev/null +++ b/srcs/nginx/src/pages/TournamentMenuPage.tsx @@ -0,0 +1,15 @@ +import { CircleButton } from '../components/atoms/CircleButton'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + +export default function TournamentMenuPage() { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( +
+ navigate('list')}>{t('game.participate')} + navigate('create')}>{t('game.create')} +
+ ); +} diff --git a/srcs/nginx/src/pages/TournamentPage.tsx b/srcs/nginx/src/pages/TournamentPage.tsx new file mode 100644 index 00000000..ba816d0b --- /dev/null +++ b/srcs/nginx/src/pages/TournamentPage.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from 'react-i18next'; +import { TournamentBracket } from '../components/molecules/TournamentBracket'; +import { Player } from '../types/types'; + +/* The principle of the tournament page: + * The creator is displayed first, then the players join sequentially. + * The three other slots are tournament participation slots; + * they are initially initialized by this function, + * and the "waiting" status corresponds to that slot. + */ +export function createWaitingPlayer(label: string): Player { + return { + id: 'waiting', + name: label, + avatar: null, + online: false, + status: 'waiting', + }; +} + +export default function TournamentPage() { + const { t } = useTranslation(); + const MOCK_PLAYERS: [Player, Player, Player, Player] = [ + { id: '1', name: 'johnny', avatar: null, online: true, status: 'connected' }, + { id: '2', name: 'eddy', avatar: null, online: false, status: 'connected' }, + { id: '3', name: 'khaled', avatar: null, online: true, status: 'connected' }, + createWaitingPlayer(t('game.waitiing')), + ] as const; + return ; +} diff --git a/srcs/nginx/src/pages/TournamentsListPage.tsx b/srcs/nginx/src/pages/TournamentsListPage.tsx new file mode 100644 index 00000000..b2984e67 --- /dev/null +++ b/srcs/nginx/src/pages/TournamentsListPage.tsx @@ -0,0 +1,50 @@ +import { + TournamentTableDesktop, + TournamentListMobile, + Tournament, +} from '../components/atoms/TournamentList'; +import { useNavigate } from 'react-router-dom'; + +const MOCK_TOURNAMENTS: Tournament[] = [ + { + id: '1', + name: 'Spin Cup #42', + players: 2, + maxPlayers: 4, + status: 'WAITING', + createdAt: '2026-02-01', + }, + { + id: '2', + name: 'Weekly Pong', + players: 4, + maxPlayers: 4, + status: 'IN_PROGRESS', + createdAt: '2026-02-03', + }, +]; + +/* + * This component links to two other components depending on the media because + * tables do not display correctly on mobile devices. + */ +export default function TournamentsListPage() { + const navigate = useNavigate(); + return ( + <> +
+ navigate(`/tournaments/${id}`)} + /> +
+ +
+ navigate(`/tournaments/${id}`)} + /> +
+ + ); +} diff --git a/srcs/nginx/src/providers/AuthProvider.tsx b/srcs/nginx/src/providers/AuthProvider.tsx index 55da3c48..2b5e99a0 100644 --- a/srcs/nginx/src/providers/AuthProvider.tsx +++ b/srcs/nginx/src/providers/AuthProvider.tsx @@ -1,44 +1,58 @@ import { ProfileSimpleDTO } from '@transcendence/core'; -import { createContext, useContext, useMemo, useState } from 'react'; +import { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { AuthContextType, AuthProviderProps } from '../types/react-types'; - -// from https://dev.to/joodi/useauth-hook-in-react-1bp3 +import { authApi } from '../api/auth-api'; export const AuthContext = createContext(null); export const AuthProvider = ({ children }: AuthProviderProps) => { - const [user, setUser] = useState(() => { - const storedUser = localStorage.getItem('user'); - return storedUser ? JSON.parse(storedUser) : null; - }); + const [user, setUser] = useState(null); + const [isAuthChecked, setIsAuthChecked] = useState(false); + + /** + * Vérification réelle de session au montage + */ + useEffect(() => { + const checkAuth = async () => { + try { + const me = await authApi.me(); + const profile: ProfileSimpleDTO = { + username: me.username, + avatarUrl: null, // ou me.avatarUrl si dispo plus tard + }; + setUser(profile); + } catch { + setUser(null); + } finally { + setIsAuthChecked(true); + } + }; + + checkAuth(); + }, []); const login = (user: ProfileSimpleDTO) => { setUser(user); - localStorage.setItem('user', JSON.stringify(user)); }; - const logout = () => { + const logout = async () => { setUser(null); - localStorage.removeItem('user'); }; const updateUser = (newUser: ProfileSimpleDTO) => { - setUser((prevUser) => { - const updated = { ...prevUser, ...newUser }; - localStorage.setItem('user', JSON.stringify(updated)); - return updated as ProfileSimpleDTO; - }); + setUser((prev) => (prev ? { ...prev, ...newUser } : prev)); }; - // memoize to avoid re-render const contextValue = useMemo( () => ({ user, + isAuthChecked, + isLoggedIn: isAuthChecked && user !== null, login, logout, updateUser, }), - [user], + [user, isAuthChecked], ); return {children}; @@ -49,8 +63,5 @@ export const useAuth = () => { if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } - - const isLoggedIn = Boolean(context.user); - - return { ...context, isLoggedIn }; + return context; }; diff --git a/srcs/nginx/src/router/TournamentRoutes.tsx b/srcs/nginx/src/router/TournamentRoutes.tsx new file mode 100644 index 00000000..33bb881b --- /dev/null +++ b/srcs/nginx/src/router/TournamentRoutes.tsx @@ -0,0 +1,22 @@ +import { Routes, Route } from 'react-router-dom'; +import TournamentLayout from '../components/organisms/TournamentLayout'; +import TournamentMenuPage from '../pages/TournamentMenuPage'; +import TournamentsListPage from '../pages/TournamentsListPage'; +import TournamentCreatePage from '../pages/TournamentCreatePage'; +import TournamentPage from '../pages/TournamentPage'; + +/* + * simplified page management with React Routes + */ +export default function TournamentRoutes() { + return ( + + }> + } /> + } /> + } /> + } /> + + + ); +} diff --git a/srcs/nginx/src/types/react-types.ts b/srcs/nginx/src/types/react-types.ts index 2bac82a9..83b42b8d 100644 --- a/srcs/nginx/src/types/react-types.ts +++ b/srcs/nginx/src/types/react-types.ts @@ -18,6 +18,8 @@ export enum Roles { export interface AuthContextType { user: ProfileSimpleDTO | null; + isLoggedIn: boolean; + isAuthChecked: boolean; login: (user: ProfileSimpleDTO) => void; logout: () => void; updateUser: (newUser: ProfileSimpleDTO) => void; diff --git a/srcs/nginx/src/types/types.ts b/srcs/nginx/src/types/types.ts index 0a12659d..e15a0e15 100644 --- a/srcs/nginx/src/types/types.ts +++ b/srcs/nginx/src/types/types.ts @@ -32,3 +32,15 @@ export interface ClientMessage { paddle?: 'left' | 'right'; direction?: 'up' | 'down' | 'stop'; } + +export type PlayerStatus = 'waiting' | 'connected'; + +export type Player = { + id: string; + name: string; + avatar: string | null; + online: boolean; + status: PlayerStatus; +}; + +export type MatchStatus = 'pending' | 'ready' | 'running' | 'finished';