diff --git a/.env.development b/.env.development
index cb4cb40..24848d3 100644
--- a/.env.development
+++ b/.env.development
@@ -1,2 +1,3 @@
# For local development (npm run dev)
-VITE_API_URL=http://localhost:8080
\ No newline at end of file
+VITE_API_URL=http://localhost:8080
+VITE_CORE_API_URL=http://localhost:8090
\ No newline at end of file
diff --git a/.env.production b/.env.production
index 658e57e..8bf2a6d 100644
--- a/.env.production
+++ b/.env.production
@@ -1,3 +1,4 @@
# For the docker image on the server
# Uses the variable AUTH_HOST from your docker structure [cite: 3]
-VITE_API_URL=https://auth.tbp.andreas-dahm.eu
\ No newline at end of file
+VITE_API_URL=https://auth.tbp.andreas-dahm.eu
+VITE_CORE_API_URL=https://api.tbp.andreas-dahm.eu
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index ae63a25..98dbe18 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,14 +8,17 @@
"name": "tobeplayed-frontend",
"version": "0.0.1",
"dependencies": {
+ "@hookform/resolvers": "^3.10.0",
"@mdi/font": "~7.4.47",
"@tailwindcss/vite": "~4.2.2",
"i18next": "~26.0.0",
"i18next-http-backend": "~3.0.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
+ "react-hook-form": "^7.73.1",
"react-i18next": "~17.0.0",
- "react-router-dom": "~7.13.2"
+ "react-router-dom": "~7.13.2",
+ "zod": "^3.25.76"
},
"devDependencies": {
"@vitejs/plugin-react": "^6.0.1",
@@ -35,20 +38,20 @@
}
},
"node_modules/@emnapi/core": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
- "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/wasi-threads": "1.2.0",
+ "@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
- "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -56,15 +59,24 @@
}
},
"node_modules/@emnapi/wasi-threads": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
- "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
+ "node_modules/@hookform/resolvers": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
+ "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react-hook-form": "^7.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -117,34 +129,36 @@
"license": "Apache-2.0"
},
"node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
- "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1",
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@oxc-project/types": {
- "version": "0.120.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz",
- "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==",
+ "version": "0.127.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
+ "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz",
- "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
"cpu": [
"arm64"
],
@@ -158,9 +172,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz",
- "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
"cpu": [
"arm64"
],
@@ -174,9 +188,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz",
- "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
"cpu": [
"x64"
],
@@ -190,9 +204,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz",
- "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
"cpu": [
"x64"
],
@@ -206,9 +220,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz",
- "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
+ "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
"cpu": [
"arm"
],
@@ -222,9 +236,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz",
- "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
"cpu": [
"arm64"
],
@@ -238,9 +252,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz",
- "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
+ "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
"cpu": [
"arm64"
],
@@ -254,9 +268,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz",
- "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
"cpu": [
"ppc64"
],
@@ -270,9 +284,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz",
- "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
"cpu": [
"s390x"
],
@@ -286,9 +300,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz",
- "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
"cpu": [
"x64"
],
@@ -302,9 +316,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz",
- "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
+ "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
"cpu": [
"x64"
],
@@ -318,9 +332,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz",
- "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
"cpu": [
"arm64"
],
@@ -334,25 +348,27 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz",
- "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
+ "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
- "@napi-rs/wasm-runtime": "^1.1.1"
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
},
"engines": {
- "node": ">=14.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz",
- "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
+ "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
"cpu": [
"arm64"
],
@@ -366,9 +382,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz",
- "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
+ "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
"cpu": [
"x64"
],
@@ -800,12 +816,12 @@
}
},
"node_modules/cross-fetch": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
- "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
+ "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"license": "MIT",
"dependencies": {
- "node-fetch": "^2.6.12"
+ "node-fetch": "^2.7.0"
}
},
"node_modules/detect-libc": {
@@ -939,12 +955,12 @@
}
},
"node_modules/i18next-http-backend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz",
- "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.5.tgz",
+ "integrity": "sha512-QaWHnsxieEDcqKe+vo/RFqpiIFRi/KBqlOSPcUlvinBaISCeiTRCbtrazHAjtHtsLC66oDsROAH8frWkQzfMMQ==",
"license": "MIT",
"dependencies": {
- "cross-fetch": "4.0.0"
+ "cross-fetch": "4.1.0"
}
},
"node_modules/jiti": {
@@ -1278,9 +1294,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
+ "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
"funding": [
{
"type": "opencollective",
@@ -1333,6 +1349,22 @@
"react": "^19.2.4"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.73.1",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.73.1.tgz",
+ "integrity": "sha512-VAfVYOPcx3piiEVQy95vyFmBwbVUsP/AUIN+mpFG8h11yshDd444nn0VyfaGWSRnhOLVgiDu7HIuBtAIzxn9dA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-i18next": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.0.tgz",
@@ -1399,13 +1431,13 @@
}
},
"node_modules/rolldown": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz",
- "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
+ "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.120.0",
- "@rolldown/pluginutils": "1.0.0-rc.10"
+ "@oxc-project/types": "=0.127.0",
+ "@rolldown/pluginutils": "1.0.0-rc.17"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -1414,27 +1446,27 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.0-rc.10",
- "@rolldown/binding-darwin-arm64": "1.0.0-rc.10",
- "@rolldown/binding-darwin-x64": "1.0.0-rc.10",
- "@rolldown/binding-freebsd-x64": "1.0.0-rc.10",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10",
- "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10",
- "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10",
- "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10",
- "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10",
- "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10",
- "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10",
- "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10",
- "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10",
- "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10"
+ "@rolldown/binding-android-arm64": "1.0.0-rc.17",
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.17",
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
}
},
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.10",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz",
- "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==",
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
+ "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
"license": "MIT"
},
"node_modules/scheduler": {
@@ -1478,13 +1510,13 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -1547,16 +1579,16 @@
}
},
"node_modules/vite": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz",
- "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==",
+ "version": "8.0.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
+ "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.8",
- "rolldown": "1.0.0-rc.10",
- "tinyglobby": "^0.2.15"
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.10",
+ "rolldown": "1.0.0-rc.17",
+ "tinyglobby": "^0.2.16"
},
"bin": {
"vite": "bin/vite.js"
@@ -1573,7 +1605,7 @@
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.1.0",
- "esbuild": "^0.27.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
@@ -1647,6 +1679,15 @@
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index ea45ce3..268f9bc 100644
--- a/package.json
+++ b/package.json
@@ -9,14 +9,17 @@
"preview": "vite preview"
},
"dependencies": {
+ "@hookform/resolvers": "^3.10.0",
"@mdi/font": "~7.4.47",
"@tailwindcss/vite": "~4.2.2",
"i18next": "~26.0.0",
"i18next-http-backend": "~3.0.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
+ "react-hook-form": "^7.73.1",
"react-i18next": "~17.0.0",
- "react-router-dom": "~7.13.2"
+ "react-router-dom": "~7.13.2",
+ "zod": "^3.25.76"
},
"devDependencies": {
"@vitejs/plugin-react": "^6.0.1",
diff --git a/public/i18n/de.json b/public/i18n/de.json
index 53f0e1d..4f1dc50 100644
--- a/public/i18n/de.json
+++ b/public/i18n/de.json
@@ -1,4 +1,4 @@
-{
+{
"IMPRINT": {
"TITLE": "Impressum",
"LEGAL": "Angaben gemäß § 5 DDG",
@@ -9,9 +9,57 @@
"EMAIL": "Email",
"PASSWORD": "Passwort",
"SUBMIT": "Spiel betreten",
+ "NO_ACCOUNT": "Noch kein Konto?",
"ERROR_EMAIL": "Gib eine gültige Email-Adresse ein mit '@' und mit '.'.",
"ERROR_PASSWORD": "Das Passwort darf nicht leer sein."
},
+ "REGISTRATION": {
+ "TITLE": "Registrieren",
+ "EMAIL": "Email",
+ "PASSWORD": "Passwort",
+ "PASSWORD_CONFIRMATION": "Passwort bestätigen",
+ "INVITE_CODE": "Einladungscode",
+ "STEAM_ID": "Steam-ID",
+ "SUBMIT": "Konto erstellen",
+ "SUCCESS": "Konto erstellt. Weiterleitung zum Login …",
+ "HAVE_ACCOUNT": "Bereits ein Konto?",
+ "ERROR_GENERIC": "Registrierung fehlgeschlagen. Bitte erneut versuchen.",
+ "ERROR_EMAIL_IN_USE": "Diese Email-Adresse ist bereits registriert.",
+ "ERROR_INVALID_INVITE_CODE": "Der Einladungscode ist ungültig oder wurde bereits verwendet.",
+ "ERROR_STEAM_ID_IN_USE": "Diese Steam-ID ist bereits mit einem anderen Konto verknüpft."
+ },
+ "SETTINGS": {
+ "TITLE": "Einstellungen",
+ "LOADING": "Einstellungen werden geladen …",
+ "ACCOUNT": "Konto",
+ "PREFERENCES": "Einstellungen",
+ "STEAM": "Steam",
+ "EMAIL": "Email",
+ "ROLE": "Rolle",
+ "CREATED_DATE": "Mitglied seit",
+ "LAST_LOGIN": "Letzter Login",
+ "DARK_THEME": "Dunkles Design",
+ "LANGUAGE": "Sprache",
+ "LANGUAGE_OPTIONS": {
+ "EN": "Englisch",
+ "DE": "Deutsch"
+ },
+ "STEAM_ID": "Steam-ID",
+ "SAVE_STEAM_ID": "Steam-ID speichern",
+ "SAVED": "Einstellungen gespeichert.",
+ "ERROR_SAVE": "Einstellungen konnten nicht gespeichert werden."
+ },
+ "VALIDATION": {
+ "EMAIL_REQUIRED": "Email ist erforderlich.",
+ "EMAIL_INVALID": "Gib eine gültige Email-Adresse ein.",
+ "PASSWORD_TOO_SHORT": "Passwort muss mindestens 8 Zeichen haben.",
+ "PASSWORD_CONFIRMATION_REQUIRED": "Bitte Passwort bestätigen.",
+ "PASSWORD_MISMATCH": "Die Passwörter stimmen nicht überein.",
+ "INVITE_CODE_REQUIRED": "Einladungscode ist erforderlich.",
+ "STEAM_ID_REQUIRED": "Steam-ID ist erforderlich.",
+ "STEAM_ID_INVALID": "Steam-ID muss eine positive Zahl sein.",
+ "LANGUAGE_INVALID": "Nicht unterstützte Sprache."
+ },
"INFO": {
"ERROR": {
"LOAD_PROFILE": "Fehler beim Laden der Profildaten.",
@@ -20,7 +68,23 @@
},
"DASHBOARD": {
"LOADING": "Lade Dashboard ...",
- "HELLO": "Hallo"
+ "HELLO": "Hallo",
+ "STEAM_GAMES": "Steam Spiele"
+ },
+ "LIBRARY": {
+ "TITLE": "Spielebibliothek",
+ "LOADING": "Lade Spiele …",
+ "EMPTY": "Noch keine Spiele. Synchronisiere deine Steam-Bibliothek.",
+ "ERROR_LOAD": "Spiele konnten nicht geladen werden.",
+ "PER_PAGE": "Pro Seite:",
+ "PREV": "Zurück",
+ "NEXT": "Weiter",
+ "PAGE_OF": "Seite {{current}} von {{total}}",
+ "GAMES": "Spiele",
+ "SYNCING": "Synchronisiere mit Steam …",
+ "SYNCING_FIRST_TIME": "Erste Synchronisation mit Steam läuft. Deine Spiele erscheinen gleich.",
+ "SYNC_FAILED": "Synchronisation fehlgeschlagen. Bitte später erneut versuchen.",
+ "LAST_SYNCED": "Zuletzt synchronisiert {{when}}"
},
"MAIN": {
"WELCOME": "Dein Game-Backlog unter Kontrolle",
@@ -29,4 +93,4 @@
"LOGIN": "Anmelden",
"LOGOUT": "Abmelden"
}
-}
\ No newline at end of file
+}
diff --git a/public/i18n/en.json b/public/i18n/en.json
index 9a503ec..d9ecdae 100644
--- a/public/i18n/en.json
+++ b/public/i18n/en.json
@@ -1,4 +1,4 @@
-{
+{
"IMPRINT": {
"TITLE": "Imprint",
"LEGAL": "Information according to § 5 TMG.",
@@ -9,9 +9,57 @@
"EMAIL": "Email",
"PASSWORD": "Password",
"SUBMIT": "Enter the game",
+ "NO_ACCOUNT": "No account yet?",
"ERROR_EMAIL": "Enter a valid email address with '@' and with '.'.",
"ERROR_PASSWORD": "The password must have at least one character."
},
+ "REGISTRATION": {
+ "TITLE": "Register",
+ "EMAIL": "Email",
+ "PASSWORD": "Password",
+ "PASSWORD_CONFIRMATION": "Confirm password",
+ "INVITE_CODE": "Invite code",
+ "STEAM_ID": "Steam ID",
+ "SUBMIT": "Create account",
+ "SUCCESS": "Account created. Redirecting to login …",
+ "HAVE_ACCOUNT": "Already have an account?",
+ "ERROR_GENERIC": "Registration failed. Please try again.",
+ "ERROR_EMAIL_IN_USE": "This email address is already registered.",
+ "ERROR_INVALID_INVITE_CODE": "The invite code is invalid or has already been used.",
+ "ERROR_STEAM_ID_IN_USE": "This Steam ID is already linked to another account."
+ },
+ "SETTINGS": {
+ "TITLE": "Settings",
+ "LOADING": "Loading settings …",
+ "ACCOUNT": "Account",
+ "PREFERENCES": "Preferences",
+ "STEAM": "Steam",
+ "EMAIL": "Email",
+ "ROLE": "Role",
+ "CREATED_DATE": "Member since",
+ "LAST_LOGIN": "Last login",
+ "DARK_THEME": "Dark theme",
+ "LANGUAGE": "Language",
+ "LANGUAGE_OPTIONS": {
+ "EN": "English",
+ "DE": "German"
+ },
+ "STEAM_ID": "Steam ID",
+ "SAVE_STEAM_ID": "Save Steam ID",
+ "SAVED": "Settings saved.",
+ "ERROR_SAVE": "Could not save settings."
+ },
+ "VALIDATION": {
+ "EMAIL_REQUIRED": "Email is required.",
+ "EMAIL_INVALID": "Enter a valid email address.",
+ "PASSWORD_TOO_SHORT": "Password must be at least 8 characters.",
+ "PASSWORD_CONFIRMATION_REQUIRED": "Please confirm your password.",
+ "PASSWORD_MISMATCH": "Passwords do not match.",
+ "INVITE_CODE_REQUIRED": "Invite code is required.",
+ "STEAM_ID_REQUIRED": "Steam ID is required.",
+ "STEAM_ID_INVALID": "Steam ID must be a positive number.",
+ "LANGUAGE_INVALID": "Unsupported language."
+ },
"INFO": {
"ERROR": {
"LOAD_PROFILE": "Error during loading of the user profile data.",
@@ -20,7 +68,23 @@
},
"DASHBOARD": {
"LOADING": "Loading dashboard ...",
- "HELLO": "Hello"
+ "HELLO": "Hello",
+ "STEAM_GAMES": "Steam Games"
+ },
+ "LIBRARY": {
+ "TITLE": "Game Library",
+ "LOADING": "Loading games …",
+ "EMPTY": "No games yet. Try syncing your Steam library.",
+ "ERROR_LOAD": "Could not load your games.",
+ "PER_PAGE": "Per page:",
+ "PREV": "Previous",
+ "NEXT": "Next",
+ "PAGE_OF": "Page {{current}} of {{total}}",
+ "GAMES": "games",
+ "SYNCING": "Syncing with Steam …",
+ "SYNCING_FIRST_TIME": "First sync with Steam in progress. Your games will appear here shortly.",
+ "SYNC_FAILED": "Sync failed. Please try again later.",
+ "LAST_SYNCED": "Last synced {{when}}"
},
"MAIN": {
"WELCOME": "Control your game backlog",
@@ -29,4 +93,4 @@
"LOGIN": "Login",
"LOGOUT": "Logout"
}
-}
\ No newline at end of file
+}
diff --git a/src/App.jsx b/src/App.jsx
index 39cacdc..e030db0 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -6,10 +6,14 @@ import LandingPage from './pages/LandingPage';
import LoginPage from './pages/LoginPage';
import ImprintPage from './pages/ImprintPage';
import Dashboard from "./pages/Dashboard.jsx";
+import RegistrationPage from './pages/RegistrationPage.jsx';
+import SettingsPage from './pages/SettingsPage.jsx';
+import GameLibrary from './pages/GameLibrary.jsx';
+import {usePreferences} from './context/PreferencesContext.jsx';
function App() {
const {t, i18n} = useTranslation();
- const [isDarkMode, setIsDarkMode] = useState(true);
+ const {isDarkMode, setDarkMode, setLanguage} = usePreferences();
const [isAuthenticated, setIsAuthenticated] = useState(false);
// Hooks for routing
@@ -17,25 +21,17 @@ function App() {
const location = useLocation();
useEffect(() => {
+ // Dev mode: inject dummy token if none exists
+ if (import.meta.env.MODE === 'development' && !localStorage.getItem('tbp_jwt')) {
+ localStorage.setItem('tbp_jwt', 'dev-dummy-token');
+ }
+
const token = localStorage.getItem('tbp_jwt');
setIsAuthenticated(!!token); // Converts the token string (or null) to a boolean
}, [location]);
- // Sync React state with the HTML class required by Tailwind CSS
- useEffect(() => {
- if (isDarkMode) {
- document.documentElement.classList.add('dark');
- } else {
- document.documentElement.classList.remove('dark');
- }
- }, [isDarkMode]);
-
const toggleLanguage = () => {
- if (i18n.language === 'de') {
- i18n.changeLanguage('en').then(_ => console.log("Changed language to 'en'"));
- } else {
- i18n.changeLanguage('de').then(_ => console.log("Changed language to 'de'"));
- }
+ setLanguage(i18n.language === 'de' ? 'en' : 'de');
};
const handleLogout = () => {
@@ -56,14 +52,19 @@ function App() {
{i18n.language.toUpperCase()}
{isAuthenticated ? (
-
+ <>
+
+
+
+
+ >
) : (
{t('MAIN.LOGIN')}
@@ -76,7 +77,10 @@ function App() {
{userData?.dashboardMessage}
+ {userData?.steamId && ( ++ {t('LOGIN.NO_ACCOUNT')}{' '} + + {t('REGISTRATION.TITLE')} + +
+ ); diff --git a/src/pages/RegistrationPage.jsx b/src/pages/RegistrationPage.jsx new file mode 100644 index 0000000..611f738 --- /dev/null +++ b/src/pages/RegistrationPage.jsx @@ -0,0 +1,185 @@ +import {useState} from 'react'; +import {Link, useNavigate} from 'react-router-dom'; +import {useTranslation} from 'react-i18next'; +import {useForm} from 'react-hook-form'; +import {zodResolver} from '@hookform/resolvers/zod'; + +import {registrationSchema} from '../utils/validation.js'; + +const BACKEND_ERROR_KEY = { + EMAIL_ALREADY_IN_USE: 'REGISTRATION.ERROR_EMAIL_IN_USE', + INVALID_INVITATION_CODE: 'REGISTRATION.ERROR_INVALID_INVITE_CODE', + STEAM_ID_ALREADY_IN_USE: 'REGISTRATION.ERROR_STEAM_ID_IN_USE', +}; + +const inputClass = + 'w-full px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-slate-50 dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:outline-none'; +const inputErrorClass = + 'w-full px-4 py-2 border border-red-500 dark:border-red-500 rounded-lg bg-slate-50 dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:outline-none'; + +export default function RegistrationPage() { + const {t} = useTranslation(); + const navigate = useNavigate(); + const [feedback, setFeedback] = useState({message: '', isError: false}); + + const { + register, + handleSubmit, + formState: {errors, isSubmitting}, + } = useForm({ + resolver: zodResolver(registrationSchema), + defaultValues: { + email: '', + password: '', + passwordConfirmation: '', + inviteCode: '', + steamId: '', + }, + }); + + const onSubmit = async (values) => { + setFeedback({message: '', isError: false}); + try { + const apiUrl = `${import.meta.env.VITE_API_URL}/api/registration/new`; + const response = await fetch(apiUrl, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + email: values.email, + password: values.password, + inviteCode: values.inviteCode, + steamId: values.steamId, + }), + }); + + if (response.ok) { + setFeedback({message: t('REGISTRATION.SUCCESS'), isError: false}); + setTimeout(() => navigate('/login'), 800); + return; + } + + const data = await response.json().catch(() => ({})); + const mappedKey = BACKEND_ERROR_KEY[data.code]; + setFeedback({ + message: mappedKey ? t(mappedKey) : data.message || t('REGISTRATION.ERROR_GENERIC'), + isError: true, + }); + } catch (err) { + console.error('Registration API error:', err); + setFeedback({message: t('INFO.ERROR.NETWORK_FAILURE'), isError: true}); + } + }; + + const renderError = (field) => + errors[field] && ( +{t(errors[field].message)}
+ ); + + return ( ++ {t('REGISTRATION.HAVE_ACCOUNT')}{' '} + + {t('MAIN.LOGIN')} + +
+{t(errors[field].message)}
+ ); + + if (isLoading) { + return ( +