diff --git a/frontend-old/index.html b/frontend-old/index.html
index 9b6bc52..1768633 100644
--- a/frontend-old/index.html
+++ b/frontend-old/index.html
@@ -4,6 +4,7 @@
+
Lose verkaufen
diff --git a/frontend-old/vite.config.ts b/frontend-old/vite.config.ts
index 8b0f57b..011a9d8 100644
--- a/frontend-old/vite.config.ts
+++ b/frontend-old/vite.config.ts
@@ -1,7 +1,6 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import { defineConfig } from "vite";
+import tailwindcss from "@tailwindcss/vite";
-// https://vite.dev/config/
export default defineConfig({
- plugins: [react()],
-})
+ plugins: [tailwindcss()],
+});
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index aad6255..59ee4ad 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -13,11 +13,18 @@
"@fontsource-variable/inter": "^5.2.8",
"@fontsource/inter": "^5.2.8",
"@mui/joy": "^5.0.0-beta.52",
+ "@tailwindcss/vite": "^4.3.0",
+ "i18next": "^26.0.10",
+ "js-cookie": "^3.0.5",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-i18next": "^17.0.7",
+ "react-router-dom": "^7.15.0",
+ "tailwindcss": "^4.3.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
+ "@types/js-cookie": "^3.0.6",
"@types/node": "^24.12.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
@@ -278,7 +285,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -290,7 +296,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -301,7 +306,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -724,7 +728,6 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -988,7 +991,6 @@
"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==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1007,7 +1009,6 @@
"version": "0.128.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz",
"integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
@@ -1030,7 +1031,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1047,7 +1047,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1064,7 +1063,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1081,7 +1079,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1098,7 +1095,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1115,7 +1111,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1132,7 +1127,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1149,7 +1143,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1166,7 +1159,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1183,7 +1175,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1200,7 +1191,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1217,7 +1207,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1234,7 +1223,6 @@
"cpu": [
"wasm32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1253,7 +1241,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1270,7 +1257,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1287,11 +1273,267 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz",
+ "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.21.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz",
+ "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.0",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.0",
+ "@tailwindcss/oxide-darwin-x64": "4.3.0",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.0",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.0",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.0",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.0",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.0",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz",
+ "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz",
+ "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz",
+ "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz",
+ "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz",
+ "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz",
+ "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz",
+ "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz",
+ "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz",
+ "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz",
+ "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.10.0",
+ "@emnapi/runtime": "^1.10.0",
+ "@emnapi/wasi-threads": "^1.2.1",
+ "@napi-rs/wasm-runtime": "^1.1.4",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz",
+ "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz",
+ "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz",
+ "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.3.0",
+ "@tailwindcss/oxide": "4.3.0",
+ "tailwindcss": "4.3.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1312,6 +1554,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1323,7 +1572,7 @@
"version": "24.12.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.3.tgz",
"integrity": "sha512-8oljBDGun9cIsZRJR6fkihn0TSXJI0UDOOhncYaERq6M0JMDoPLxyscwruJcb4GKS6dvK/d8xebYBg27h/duaQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
@@ -1801,6 +2050,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -1875,7 +2137,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -1888,6 +2149,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.21.2",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz",
+ "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
@@ -2135,7 +2409,6 @@
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -2210,7 +2483,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -2266,6 +2538,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
"node_modules/hasown": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
@@ -2310,6 +2588,43 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/i18next": {
+ "version": "26.0.10",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.10.tgz",
+ "integrity": "sha512-k3yGPAlWR2RdMYoVXJoDZDT87qeHIWKH7gVksdZMpRty7QX/D9QZeYGvN08KGbKHke9wn01eYT+EEsrqX/YTlw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://www.locize.com/i18next"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.locize.com"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2397,6 +2712,24 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2483,7 +2816,6 @@
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
- "dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -2516,7 +2848,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2537,7 +2868,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2558,7 +2888,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2579,7 +2908,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2600,7 +2928,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2621,7 +2948,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2642,7 +2968,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2663,7 +2988,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2684,7 +3008,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2705,7 +3028,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2726,7 +3048,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2784,6 +3105,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
@@ -2810,7 +3140,6 @@
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -2973,7 +3302,6 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -2986,7 +3314,6 @@
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3069,12 +3396,77 @@
"react": "^19.2.6"
}
},
+ "node_modules/react-i18next": {
+ "version": "17.0.7",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.7.tgz",
+ "integrity": "sha512-rwtPXsb/zwzDafN+gytcjF5YnqGQQIRmCQ6DctBC1VSipRB8GD/MWEVrFP42vjMyuYydxWxM8CZRt+yiNuuoHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.29.2",
+ "html-parse-stringify": "^3.0.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "i18next": ">= 26.0.10",
+ "react": ">= 16.8.0",
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "19.2.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz",
"integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==",
"license": "MIT"
},
+ "node_modules/react-router": {
+ "version": "7.15.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz",
+ "integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.15.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.0.tgz",
+ "integrity": "sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.15.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
@@ -3109,7 +3501,6 @@
"version": "1.0.0-rc.18",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
"integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.128.0",
@@ -3143,7 +3534,6 @@
"version": "1.0.0-rc.18",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz",
"integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==",
- "dev": true,
"license": "MIT"
},
"node_modules/scheduler": {
@@ -3162,6 +3552,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3198,7 +3594,6 @@
"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==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -3222,11 +3617,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwindcss": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
+ "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -3256,7 +3669,6 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
"license": "0BSD",
"optional": true
},
@@ -3277,7 +3689,7 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -3315,7 +3727,7 @@
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/update-browserslist-db": {
@@ -3359,11 +3771,19 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/vite": {
"version": "8.0.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz",
"integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==",
- "dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
@@ -3437,6 +3857,15 @@
}
}
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3474,7 +3903,6 @@
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
- "dev": true,
"license": "ISC",
"optional": true,
"peer": true,
diff --git a/frontend/package.json b/frontend/package.json
index 10be99b..9566b6e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,8 +15,14 @@
"@fontsource-variable/inter": "^5.2.8",
"@fontsource/inter": "^5.2.8",
"@mui/joy": "^5.0.0-beta.52",
+ "@tailwindcss/vite": "^4.3.0",
+ "i18next": "^26.0.10",
+ "js-cookie": "^3.0.5",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-i18next": "^17.0.7",
+ "react-router-dom": "^7.15.0",
+ "tailwindcss": "^4.3.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -24,6 +30,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
+ "@types/js-cookie": "^3.0.6",
"@types/node": "^24.12.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
diff --git a/frontend/src/App.css b/frontend/src/App.css
index f90339d..e69de29 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -1,184 +0,0 @@
-.counter {
- font-size: 16px;
- padding: 5px 10px;
- border-radius: 5px;
- color: var(--accent);
- background: var(--accent-bg);
- border: 2px solid transparent;
- transition: border-color 0.3s;
- margin-bottom: 24px;
-
- &:hover {
- border-color: var(--accent-border);
- }
- &:focus-visible {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
- }
-}
-
-.hero {
- position: relative;
-
- .base,
- .framework,
- .vite {
- inset-inline: 0;
- margin: 0 auto;
- }
-
- .base {
- width: 170px;
- position: relative;
- z-index: 0;
- }
-
- .framework,
- .vite {
- position: absolute;
- }
-
- .framework {
- z-index: 1;
- top: 34px;
- height: 28px;
- transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
- scale(1.4);
- }
-
- .vite {
- z-index: 0;
- top: 107px;
- height: 26px;
- width: auto;
- transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
- scale(0.8);
- }
-}
-
-#center {
- display: flex;
- flex-direction: column;
- gap: 25px;
- place-content: center;
- place-items: center;
- flex-grow: 1;
-
- @media (max-width: 1024px) {
- padding: 32px 20px 24px;
- gap: 18px;
- }
-}
-
-#next-steps {
- display: flex;
- border-top: 1px solid var(--border);
- text-align: left;
-
- & > div {
- flex: 1 1 0;
- padding: 32px;
- @media (max-width: 1024px) {
- padding: 24px 20px;
- }
- }
-
- .icon {
- margin-bottom: 16px;
- width: 22px;
- height: 22px;
- }
-
- @media (max-width: 1024px) {
- flex-direction: column;
- text-align: center;
- }
-}
-
-#docs {
- border-right: 1px solid var(--border);
-
- @media (max-width: 1024px) {
- border-right: none;
- border-bottom: 1px solid var(--border);
- }
-}
-
-#next-steps ul {
- list-style: none;
- padding: 0;
- display: flex;
- gap: 8px;
- margin: 32px 0 0;
-
- .logo {
- height: 18px;
- }
-
- a {
- color: var(--text-h);
- font-size: 16px;
- border-radius: 6px;
- background: var(--social-bg);
- display: flex;
- padding: 6px 12px;
- align-items: center;
- gap: 8px;
- text-decoration: none;
- transition: box-shadow 0.3s;
-
- &:hover {
- box-shadow: var(--shadow);
- }
- .button-icon {
- height: 18px;
- width: 18px;
- }
- }
-
- @media (max-width: 1024px) {
- margin-top: 20px;
- flex-wrap: wrap;
- justify-content: center;
-
- li {
- flex: 1 1 calc(50% - 8px);
- }
-
- a {
- width: 100%;
- justify-content: center;
- box-sizing: border-box;
- }
- }
-}
-
-#spacer {
- height: 88px;
- border-top: 1px solid var(--border);
- @media (max-width: 1024px) {
- height: 48px;
- }
-}
-
-.ticks {
- position: relative;
- width: 100%;
-
- &::before,
- &::after {
- content: '';
- position: absolute;
- top: -4.5px;
- border: 5px solid transparent;
- }
-
- &::before {
- left: 0;
- border-left-color: var(--border);
- }
- &::after {
- right: 0;
- border-right-color: var(--border);
- }
-}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d49c9e8..b14f593 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,8 +1,17 @@
import "./App.css";
-import Button from "@mui/joy/Button";
+import { BrowserRouter, Route, Routes } from "react-router-dom";
+import { MainForm } from "./pages/MainForm";
+import { SuccessPage } from "./pages/SuccessPage";
function App() {
- return ;
+ return (
+
+
+ } />
+ } />
+
+
+ );
}
export default App;
diff --git a/frontend/src/config/api.config.ts b/frontend/src/config/api.config.ts
new file mode 100644
index 0000000..fc9d0f3
--- /dev/null
+++ b/frontend/src/config/api.config.ts
@@ -0,0 +1,4 @@
+export const API_BASE =
+ (import.meta as any).env?.VITE_BACKEND_URL ||
+ import.meta.env.VITE_BACKEND_URL ||
+ "http://localhost:8004";
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 5fb3313..e69de29 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,111 +0,0 @@
-:root {
- --text: #6b6375;
- --text-h: #08060d;
- --bg: #fff;
- --border: #e5e4e7;
- --code-bg: #f4f3ec;
- --accent: #aa3bff;
- --accent-bg: rgba(170, 59, 255, 0.1);
- --accent-border: rgba(170, 59, 255, 0.5);
- --social-bg: rgba(244, 243, 236, 0.5);
- --shadow:
- rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
-
- --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
- --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
- --mono: ui-monospace, Consolas, monospace;
-
- font: 18px/145% var(--sans);
- letter-spacing: 0.18px;
- color-scheme: light dark;
- color: var(--text);
- background: var(--bg);
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-
- @media (max-width: 1024px) {
- font-size: 16px;
- }
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --text: #9ca3af;
- --text-h: #f3f4f6;
- --bg: #16171d;
- --border: #2e303a;
- --code-bg: #1f2028;
- --accent: #c084fc;
- --accent-bg: rgba(192, 132, 252, 0.15);
- --accent-border: rgba(192, 132, 252, 0.5);
- --social-bg: rgba(47, 48, 58, 0.5);
- --shadow:
- rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
- }
-
- #social .button-icon {
- filter: invert(1) brightness(2);
- }
-}
-
-#root {
- width: 1126px;
- max-width: 100%;
- margin: 0 auto;
- text-align: center;
- border-inline: 1px solid var(--border);
- min-height: 100svh;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
-}
-
-h1,
-h2 {
- font-family: var(--heading);
- font-weight: 500;
- color: var(--text-h);
-}
-
-h1 {
- font-size: 56px;
- letter-spacing: -1.68px;
- margin: 32px 0;
- @media (max-width: 1024px) {
- font-size: 36px;
- margin: 20px 0;
- }
-}
-h2 {
- font-size: 24px;
- line-height: 118%;
- letter-spacing: -0.24px;
- margin: 0 0 8px;
- @media (max-width: 1024px) {
- font-size: 20px;
- }
-}
-p {
- margin: 0;
-}
-
-code,
-.counter {
- font-family: var(--mono);
- display: inline-flex;
- border-radius: 4px;
- color: var(--text-h);
-}
-
-code {
- font-size: 15px;
- line-height: 135%;
- padding: 4px 8px;
- background: var(--code-bg);
-}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202..63fd9f8 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,10 +1,11 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.tsx'
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./utils/i18n/index.ts";
+import "./index.css";
+import App from "./App.tsx";
-createRoot(document.getElementById('root')!).render(
+createRoot(document.getElementById("root")!).render(
,
-)
+);
diff --git a/frontend/src/pages/MainForm.tsx b/frontend/src/pages/MainForm.tsx
new file mode 100644
index 0000000..3464a5a
--- /dev/null
+++ b/frontend/src/pages/MainForm.tsx
@@ -0,0 +1,514 @@
+import {
+ TextField,
+ FormControlLabel,
+ Checkbox,
+ Button,
+ Alert,
+ CircularProgress,
+ Autocomplete,
+ Chip,
+ Box,
+ Paper,
+ Typography,
+ IconButton,
+} from "@mui/material";
+import { useTranslation } from "../../node_modules/react-i18next";
+import { useState, useEffect } from "react";
+import { submitFormData } from "../utils/sender";
+import Cookies from "../../node_modules/@types/js-cookie";
+import * as React from "react";
+import TranslateIcon from "@mui/icons-material/Translate";
+import { API_BASE } from "../config/api.config";
+
+interface Message {
+ type: "error" | "info" | "success" | "warning";
+ headline: string;
+ text: string;
+}
+
+export const MainForm = () => {
+ const { t, i18n } = useTranslation();
+ const [invoice, setInvoice] = useState(false);
+ const [msg, setMsg] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [nextID, setNextID] = useState(null);
+ const [formData, setFormData] = useState({
+ firstName: "",
+ lastName: "",
+ email: "",
+ phoneNumber: "",
+ tickets: 1,
+ companyName: "",
+ cmpFirstName: "",
+ cpmLastName: "",
+ cpmEmail: "",
+ cpmPhoneNumber: "",
+ street: "",
+ postalCode: "",
+ paymentMethod: "",
+ });
+ const [users, setUsers] = useState([]);
+ const [selectedUser, setSelectedUser] = useState(null);
+
+ const changeTranslation = () => {
+ const clientLng = i18n.language;
+
+ if (clientLng === "en") {
+ i18n.changeLanguage("de");
+ } else if (clientLng === "de") {
+ i18n.changeLanguage("en");
+ } else {
+ setMsg({
+ type: "error",
+ headline: "Error",
+ text: "Cannot change langugage.",
+ });
+ }
+ };
+
+ useEffect(() => {
+ // Fetch user data or any other data needed for the form
+ try {
+ const fetchUsers = async () => {
+ const response = await fetch(`${API_BASE}/default/users`);
+ const data = await response.json();
+ setUsers(data.users);
+ };
+ fetchUsers();
+ console.log(users);
+ } catch (error) {
+ setMsg({
+ type: "error",
+ headline: t("error"),
+ text: t("failed-to-load-users"),
+ });
+ console.error("Error fetching users:", error);
+ }
+
+ if (Cookies.get("selectedUser")) {
+ const cookieUser = Cookies.get("selectedUser")!;
+ setSelectedUser(cookieUser);
+ confirmUser(cookieUser);
+ }
+ }, [isLoading]);
+
+ const handleChange = (e: React.ChangeEvent) => {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ };
+
+ const confirmUser = async (selectedUser: string) => {
+ try {
+ const response = await fetch(
+ `${API_BASE}/default/confirm-user?username=${selectedUser}`,
+ );
+ const data = await response.json();
+ setNextID(data.nextID);
+ } catch (error) {
+ console.error("Error confirming user:", error);
+ }
+ };
+
+ const handleUserSelection = (selectedUser: string | null) => {
+ if (!selectedUser) return;
+ setSelectedUser(selectedUser);
+ confirmUser(selectedUser);
+ Cookies.set("selectedUser", selectedUser);
+ };
+
+ const handleSubmit = async () => {
+ setIsLoading(true);
+ try {
+ const result = await submitFormData(formData, selectedUser || "");
+ if (result.success) {
+ document.location.href = `/success?id=${nextID}&tickets=${formData.tickets}`;
+ } else {
+ setMsg({
+ type: "error",
+ headline: t("error"),
+ text: result.error || t("form-submission-failed"),
+ });
+ }
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ changeTranslation()}
+ aria-label="translate"
+ >
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/pages/SuccessPage.tsx b/frontend/src/pages/SuccessPage.tsx
new file mode 100644
index 0000000..aa39c17
--- /dev/null
+++ b/frontend/src/pages/SuccessPage.tsx
@@ -0,0 +1,210 @@
+import { Box, Paper, Typography, Chip, Button } from "@mui/material";
+import { useEffect, useState } from "react";
+import { CircleCheck } from "lucide-react";
+import { useTranslation } from "../../node_modules/react-i18next";
+
+export const SuccessPage = () => {
+ const [orderId, setOrderId] = useState(null);
+ const [tickets, setNumberOfTickets] = useState(0);
+ const [animate, setAnimate] = useState(false);
+ const { t } = useTranslation();
+ const [seconds, setSeconds] = useState(30);
+
+ useEffect(() => {
+ const params = new URLSearchParams(window.location.search);
+ const id = params.get("id");
+ const numberOfTickets = params.get("tickets");
+
+ setOrderId(id);
+ setNumberOfTickets(numberOfTickets ? parseInt(numberOfTickets, 10) : 0);
+
+ setTimeout(() => setAnimate(true), 100);
+ }, []);
+
+ useEffect(() => {
+ if (seconds === 0) {
+ window.location.href = "/";
+ return;
+ }
+
+ const timer = setTimeout(() => setSeconds(seconds - 1), 1000);
+
+ return () => clearTimeout(timer);
+ }, [seconds]);
+
+ return (
+
+
+ {/* Animated Success Icon */}
+
+
+
+
+ {/* Success Message */}
+
+ {t("form-submitted-successfully")}
+
+
+
+ {t("ticket-payment", { count: tickets })}
+
+
+ {/* Tickets Display */}
+ {tickets > 0 && (
+
+
+
+ )}
+
+ {/* Order ID Display */}
+ {orderId && (
+
+
+ {t("entry-id")}
+
+
+
+ )}
+
+ {/* Return button */}
+
+
+
+
+ {/* Additional Info */}
+
+
+ {t("thank-you")}
+
+
+
+ {/* Decorative Elements */}
+
+
+
+ );
+};
diff --git a/frontend/src/utils/i18n/index.ts b/frontend/src/utils/i18n/index.ts
new file mode 100644
index 0000000..dcfa293
--- /dev/null
+++ b/frontend/src/utils/i18n/index.ts
@@ -0,0 +1,34 @@
+import i18n from "i18next";
+import { initReactI18next } from "react-i18next";
+import Cookies from "js-cookie";
+
+import enLang from "./locales/en/en.json";
+import deLang from "./locales/de/de.json";
+
+// the translations
+// (tip move them in a JSON file and import them,
+// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files)
+const resources = {
+ en: {
+ translation: enLang,
+ },
+ de: {
+ translation: deLang,
+ },
+};
+
+i18n
+ .use(initReactI18next) // passes i18n down to react-i18next
+ .init({
+ resources,
+ fallbackLng: "en", // use en if detected lng is not available
+ lng: Cookies.get("language") || navigator.language.split("-")[0] || "en", // Check cookie first, then browser language
+ // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
+ // if you're using a language detector, do not define the lng option
+
+ interpolation: {
+ escapeValue: false, // react already safes from xss
+ },
+ });
+
+export default i18n;
diff --git a/frontend/src/utils/i18n/locales/de/de.json b/frontend/src/utils/i18n/locales/de/de.json
new file mode 100644
index 0000000..6033c4b
--- /dev/null
+++ b/frontend/src/utils/i18n/locales/de/de.json
@@ -0,0 +1,28 @@
+{
+ "first-name": "Vorname",
+ "last-name": "Nachname",
+ "phone-number": "Telefonnummer",
+ "tickets": "Lose",
+ "invoice": "Rechnung",
+ "company-name": "Firmenname",
+ "street": "Straße + Haus Nr.",
+ "postal-code": "Plz + Stadt",
+ "email": "E-Mail",
+ "submit": "Abschicken",
+ "failed-to-load-users": "Das Laden der Benutzer ist fehlgeschlagen.",
+ "user": "Benutzer",
+ "next-id": "Nächste Eintragsnummer: ",
+ "form-submitted-successfully": "Formular erfolgreich übermittelt!",
+ "orm-submission-failed": "Formularübermittlung fehlgeschlagen.",
+ "success": "Erfolg",
+ "error": "Fehler",
+ "cash": "Bar",
+ "paypal": "PayPal",
+ "transfer": "Andere (notieren)",
+ "ticket-payment_one": "Sie haben erfolgreich {{count}} Los gekauft.",
+ "ticket-payment_other": "Sie haben erfolgreich {{count}} Lose gekauft.",
+ "entry-id": "Eintrags-ID",
+ "thank-you": "Vielen Dank für Ihre Unterstützung der Claudius Akademie! Wir wünschen Ihnen viel Glück mit dem Los.",
+ "select-payment-method": "Zahlungsmethode auswählen",
+ "return-to-homepage": "Zurück"
+}
\ No newline at end of file
diff --git a/frontend/src/utils/i18n/locales/en/en.json b/frontend/src/utils/i18n/locales/en/en.json
new file mode 100644
index 0000000..e52ae2a
--- /dev/null
+++ b/frontend/src/utils/i18n/locales/en/en.json
@@ -0,0 +1,29 @@
+{
+ "first-name": "First Name",
+ "last-name": "Last Name",
+ "phone-number": "Phone Number",
+ "invoice": "Invoice",
+ "company-name": "Company Name",
+ "street": "Street + House No.",
+ "postal-code": "Postal Code + City",
+ "email": "Email",
+ "submit": "Submit Form",
+ "failed-to-load-users": "Failed to load users.",
+ "user": "User",
+ "next-id": "Next Entry Number: ",
+ "form-submitted-successfully": "Form submitted successfully!",
+ "orm-submission-failed": "Form submission failed.",
+ "success": "Success",
+ "error": "Error",
+ "cash": "Cash",
+ "paypal": "PayPal",
+ "transfer": "Other (note down)",
+ "ticket-payment_one": "You have successfully purchased {{count}} ticket.",
+ "ticket-payment_other": "You have successfully purchased {{count}} tickets.",
+ "ticket": "Ticket",
+ "tickets": "Tickets",
+ "entry-id": "Entry ID",
+ "thank-you": "Thank you for supporting the Claudius Akademie! We wish you the best of luck with your ticket.",
+ "select-payment-method": "Select Payment Method",
+ "return-to-homepage": "Return"
+}
\ No newline at end of file
diff --git a/frontend/src/utils/sender.ts b/frontend/src/utils/sender.ts
new file mode 100644
index 0000000..b61319c
--- /dev/null
+++ b/frontend/src/utils/sender.ts
@@ -0,0 +1,42 @@
+import { API_BASE } from "../config/api.config";
+
+interface FormData {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phoneNumber: string;
+ tickets: number;
+ companyName: string;
+ cmpFirstName: string;
+ cpmLastName: string;
+ cpmEmail: string;
+ cpmPhoneNumber: string;
+ street: string;
+ postalCode: string;
+ paymentMethod: string;
+}
+
+export const submitFormData = async (data: FormData, username: string) => {
+ console.log(data);
+ try {
+ const response = await fetch(
+ `${API_BASE}/default/new-entry?username=${username}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ },
+ );
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ return { success: false, error: `Server error: ${errorText}` };
+ }
+
+ return { success: true };
+ } catch (error) {
+ return { success: false, error: (error as Error).message };
+ }
+};