diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 851608d..95e7dff 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,9 @@ "@emotion/styled": "^11.14.1", "@fontsource-variable/inter": "^5.2.8", "@fontsource/inter": "^5.2.8", + "@mui/icons-material": "^9.0.1", "@mui/joy": "^5.0.0-beta.52", + "@mui/material": "^9.0.1", "@tailwindcss/vite": "^4.3.0", "i18next": "^26.0.10", "js-cookie": "^3.0.5", @@ -803,6 +805,32 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.0.1.tgz", + "integrity": "sha512-5PRpQjVLTNLyV/2J9J53Yz4R0tVbodG0BQDN2zQI1QBG1OPYM25ar+4N20eyFOfJT6zKglLzsnU70+zdVLaTkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^9.0.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/joy": { "version": "5.0.0-beta.52", "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.52.tgz", @@ -844,6 +872,213 @@ } } }, + "node_modules/@mui/material": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.0.1.tgz", + "integrity": "sha512-voyCpeUxcSWLN7KPZuq0pGCIt726T9K6kiVM3XUcywZDAlZSarLHaUxJVQpospbjjOzN53hwyjo8s6KoWl6utw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/core-downloads-tracker": "^9.0.1", + "@mui/system": "^9.0.1", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.4", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^9.0.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/core-downloads-tracker": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.0.1.tgz", + "integrity": "sha512-GzamIIhZ1bH77dq7eKaeyRgJdkypsxin4jBFq2EMs4lBWRR0LFO1CSVMsoebn/VvjcNrnrOrjy48MkrkQUK2iw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material/node_modules/@mui/private-theming": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.0.1.tgz", + "integrity": "sha512-pSIGq4Yw749KHEwlkYZWVERgHgwJELP6ODtBNUfV8V4oIb5H+h7IQDFXuk/b2oQccODK1enJAtiEzlgLZmq+8g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/utils": "^9.0.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/styled-engine": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.0.0.tgz", + "integrity": "sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/system": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.0.1.tgz", + "integrity": "sha512-WvlioaLxk6ewUIOfh0StxUvOPDS1mCfzaulcudsL1brZNXuh0N9FMk7RpH7ImJKjEz412SEy/V/yvqmtxbqxCQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/private-theming": "^9.0.1", + "@mui/styled-engine": "^9.0.0", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.1", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.0.0.tgz", + "integrity": "sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/utils": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.0.1.tgz", + "integrity": "sha512-f3UO3jNN1pYg5zxqXC81Bvv8hx5ACcYc0387382ZI7M5ono1heIwHYLrKsz85myguWdeVKPRZGmDdynWUBjK2g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.0.0", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/private-theming": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz", @@ -1595,7 +1830,6 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1611,6 +1845,15 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", @@ -2143,6 +2386,16 @@ "node": ">=8" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.353", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", @@ -3477,6 +3730,22 @@ "react-dom": ">=18" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", diff --git a/frontend/package.json b/frontend/package.json index a4ee550..ed61e3e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,9 @@ "@emotion/styled": "^11.14.1", "@fontsource-variable/inter": "^5.2.8", "@fontsource/inter": "^5.2.8", + "@mui/icons-material": "^9.0.1", "@mui/joy": "^5.0.0-beta.52", + "@mui/material": "^9.0.1", "@tailwindcss/vite": "^4.3.0", "i18next": "^26.0.10", "js-cookie": "^3.0.5", diff --git a/frontend/src/App.css b/frontend/src/App.css index e69de29..7b53bf9 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -0,0 +1,8 @@ +@import "tailwindcss"; + +html, +body, +#root { + height: 100%; + margin: 0; +} diff --git a/frontend/src/index.css b/frontend/src/index.css index e69de29..6165f25 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -0,0 +1,12 @@ +@import "tailwindcss"; + +html, +body, +#root { + height: 100%; + margin: 0; +} + +html { + font-size: large; +} diff --git a/frontend/src/pages/MainForm.tsx b/frontend/src/pages/MainForm.tsx index d6df471..3edad8b 100644 --- a/frontend/src/pages/MainForm.tsx +++ b/frontend/src/pages/MainForm.tsx @@ -2,7 +2,6 @@ import { useTranslation } from "react-i18next"; import { useState, useEffect } from "react"; import * as React from "react"; import Cookies from "js-cookie"; -import { Languages } from "lucide-react"; import { Sheet, Input, @@ -15,10 +14,18 @@ import { FormControl, FormLabel, Autocomplete, + ButtonGroup, + Modal, + ModalDialog, + ModalClose, } from "@mui/joy"; import { submitFormData } from "../utils/sender"; import { API_BASE } from "../config/api.config"; import type { FormData, Message } from "../config/interfaces.config"; +import PersonIcon from "@mui/icons-material/Person"; +import QrCodeIcon from "@mui/icons-material/QrCode"; +import TranslateIcon from "@mui/icons-material/Translate"; +import qrCode from "../assets/PayPal-QR-Code.png"; const PAYMENT_METHODS = ["bar", "paypal", "andere"] as const; const PAYMENT_LABELS: Record = { @@ -83,6 +90,8 @@ export const MainForm = () => { const [users, setUsers] = useState([]); const [selectedUser, setSelectedUser] = useState(null); const [formData, setFormData] = useState(DEFAULT_FORM); + const [showSelectUser, setShowSelectUser] = useState(false); + const [QRmodal, setQRmodal] = useState(false); const handleChange = (e: React.ChangeEvent) => { setFormData({ ...formData, [e.target.name]: e.target.value }); @@ -107,10 +116,30 @@ export const MainForm = () => { Cookies.set("selectedUser", username); }; - const toggleLanguage = () => { - i18n.changeLanguage(i18n.language === "en" ? "de" : "en"); + const changeTranslation = () => { + const clientLng = i18n.language; + + if (clientLng === "en") { + i18n.changeLanguage("de"); + Cookies.set("language", "de"); + } else if (clientLng === "de") { + i18n.changeLanguage("en"); + Cookies.set("language", "en"); + } else { + setMsg({ + type: "danger", + headline: "Error", + text: "Cannot change langugage.", + }); + } }; + useEffect(() => { + if (formData.paymentMethod === "paypal") { + setQRmodal(true); + } + }, [formData.paymentMethod]); + useEffect(() => { (async () => { try { @@ -155,38 +184,11 @@ export const MainForm = () => { const fieldProps = { formData, onChange: handleChange }; return ( -
- - {/* Language toggle */} - - - - -
{ - e.preventDefault(); - handleSubmit(); - }} - className="flex flex-col gap-4" - > + <> + + + setShowSelectUser(false)} /> + {t("user")} {/* User selection */} { if (e.key === "Enter") e.preventDefault(); }} /> - - {/* Next ID badge */} - + + + + setQRmodal(false)} /> + {t("qr-text")} + PayPal QR Code - #{nextID ?? "N/A"} - - - {/* Name row */} -
- - -
- - - +
+
- {/* Tickets + Invoice toggle */} -
- - {t("tickets")} - - -
- setInvoice(e.target.checked)} - label={t("invoice")} - variant="outlined" - /> +
+ + + setShowSelectUser(true)}> + + + setQRmodal(true)}> + + + {/* Language toggle */} + + + + + + { + e.preventDefault(); + handleSubmit(); + }} + className="flex flex-col gap-4" + > + {/* Next ID badge */} + + #{nextID ?? "N/A"} + + + {/* Name row */} +
+ +
-
- {/* Invoice details (conditional) */} - {invoice && ( -
- - {t("invoice-details")} - - -
+ + + + {/* Tickets + Invoice toggle */} +
+ + {t("tickets")} + + +
+ setInvoice(e.target.checked)} + label={t("invoice")} + variant="outlined" + /> +
+
+ + {/* Invoice details (conditional) */} + {invoice && ( +
+ + {t("invoice-details")} + +
+ + +
+ + +
- - - - -
- )} - - {/* Payment method selection */} - - {t("select-payment-method")} * -
- {PAYMENT_METHODS.map((method) => ( - - ))} -
- {/* Hidden required input to enforce payment selection on submit */} - {!formData.paymentMethod && ( - {}} - style={{ - opacity: 0, - width: 0, - height: 0, - position: "absolute", - }} - /> )} -
- {/* Submit button */} - + {/* Payment method selection */} + + {t("select-payment-method")} +
+ {PAYMENT_METHODS.map((method) => ( + + ))} +
+ {/* Hidden required input to enforce payment selection on submit */} + {!formData.paymentMethod && ( + {}} + style={{ + opacity: 0, + width: 0, + height: 0, + position: "absolute", + }} + /> + )} +
- {/* Alert message */} - {msg && ( - - {msg.headline}: {msg.text} - - )} - - -
+ {/* Submit button */} + + + {/* Alert message */} + {msg && ( + + {msg.headline}: {msg.text} + + )} + + +
+ ); }; diff --git a/frontend/src/utils/i18n/locales/de/de.json b/frontend/src/utils/i18n/locales/de/de.json index e98ad0c..cdaf863 100644 --- a/frontend/src/utils/i18n/locales/de/de.json +++ b/frontend/src/utils/i18n/locales/de/de.json @@ -25,5 +25,6 @@ "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" + "return-to-homepage": "Zurück", + "qr-text": "PayPal QR-Code der Claudius Akademie" } \ 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 index ccef4ad..4bde327 100644 --- a/frontend/src/utils/i18n/locales/en/en.json +++ b/frontend/src/utils/i18n/locales/en/en.json @@ -26,5 +26,6 @@ "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" + "return-to-homepage": "Return", + "qr-text": "PayPal QR-Code from the Claudius Akademie" } \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 8b0f57b..011a9d8 100644 --- a/frontend/vite.config.ts +++ b/frontend/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()], +});