From f7a0a3753c88d56ab7524b6c72fa27caac769bb4 Mon Sep 17 00:00:00 2001 From: Theis Date: Mon, 25 May 2026 11:32:45 +0200 Subject: [PATCH 1/3] refactored code --- frontend/src/pages/MainForm.tsx | 36 ++++++++++----------------------- frontend/src/utils/uxFncs.ts | 26 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/frontend/src/pages/MainForm.tsx b/frontend/src/pages/MainForm.tsx index 36a5df2..04c274b 100644 --- a/frontend/src/pages/MainForm.tsx +++ b/frontend/src/pages/MainForm.tsx @@ -27,37 +27,15 @@ import { SelectUserModal } from "../components/modals/SelectUserModal"; import { useForm } from "@tanstack/react-form"; import { changeTranslation } from "../utils/uxFncs"; import { createFormSchema } from "../config/interfaces.config"; -import type { ZodObject, ZodRawShape } from "zod"; +import { validateFieldWithZod } from "../utils/uxFncs"; import { TextField } from "../components/TextField"; - -const PAYMENT_METHODS = ["bar", "paypal", "andere"] as const; -const PAYMENT_LABELS: Record = { - bar: "Cash", - paypal: "PayPal", - andere: "Transfer", -}; - -/** - * Validates a single field against the full Zod schema. - * Returns the first error message for that field, or undefined if valid. - */ -function validateFieldWithZod( - schema: ZodObject, - fieldName: string, - allValues: Record, -): string | undefined { - const result = schema.safeParse(allValues); - if (result.success) return undefined; - const issue = result.error.issues.find( - (i) => i.path.length === 1 && i.path[0] === fieldName, - ); - return issue?.message; -} +import { PAYMENT_METHODS, PAYMENT_LABELS } from "../utils/uxFncs"; export const MainForm = () => { const { t } = useTranslation(); const queryClient = useQueryClient(); + // States const [invoice, setInvoice] = useState(false); const [msg, setMsg] = useState(null); const [selectedUser, setSelectedUser] = useState(null); @@ -66,6 +44,7 @@ export const MainForm = () => { const formSchema = createFormSchema(t, invoice); + // Validates the fields and makes sure that the form is maching the zod schema const makeFieldValidator = (fieldName: string) => ({ onSubmit: ({ fieldApi, @@ -85,6 +64,7 @@ export const MainForm = () => { }, }); + // Creates a form with tanstack form const { Field, Subscribe, handleSubmit, setFieldValue } = useForm({ defaultValues: { firstName: "", @@ -108,6 +88,7 @@ export const MainForm = () => { }, }); + // This function returns the errors for one field const getErrors = (field: { state: { meta: { errorMap: Record } }; }) => { @@ -122,6 +103,7 @@ export const MainForm = () => { return [...blurErrors, ...submitErrors].filter(Boolean); }; + // useEffect that only executes on mount and is checking if there is a user selcted in the cookies useEffect(() => { const savedUser = Cookies.get("selectedUser"); if (savedUser) { @@ -135,17 +117,20 @@ export const MainForm = () => { } }, []); + // Fetch users tanstack query const { data: usernameData, isLoading: usernameDataIsLoading } = useQuery({ queryKey: ["users"], queryFn: fetchUsers, }); + // Query for validating selcted user const { data: userData } = useQuery({ queryKey: ["user", selectedUser], enabled: !!selectedUser, queryFn: () => confirmUser(selectedUser), }); + // Tanstack query (mutate) for sending the form const { mutate: mutateForm, isPending: mutateFormIsPending } = useMutation({ mutationFn: (values: FormData) => submitFormData(values, selectedUser), onSuccess: (_, values) => { @@ -164,6 +149,7 @@ export const MainForm = () => { const nextID = userData?.nextID ?? "N/A"; + // function for selecting user const handleUserSelection = (username: string | null) => { if (username == null || username == "") { return; diff --git a/frontend/src/utils/uxFncs.ts b/frontend/src/utils/uxFncs.ts index e0daded..cd9a6cf 100644 --- a/frontend/src/utils/uxFncs.ts +++ b/frontend/src/utils/uxFncs.ts @@ -1,5 +1,7 @@ import i18n from "./i18n"; import Cookies from "js-cookie"; +import { ZodObject } from "zod"; +import type { ZodRawShape } from "zod"; export const changeTranslation = () => { const clientLng = i18n.language; @@ -14,3 +16,27 @@ export const changeTranslation = () => { alert("Cannot change language."); } }; + +/** + * Validates a single field against the full Zod schema. + * Returns the first error message for that field, or undefined if valid. + */ +export function validateFieldWithZod( + schema: ZodObject, + fieldName: string, + allValues: Record, +): string | undefined { + const result = schema.safeParse(allValues); + if (result.success) return undefined; + const issue = result.error.issues.find( + (i) => i.path.length === 1 && i.path[0] === fieldName, + ); + return issue?.message; +} + +export const PAYMENT_METHODS = ["bar", "paypal", "andere"] as const; +export const PAYMENT_LABELS: Record = { + bar: "Cash", + paypal: "PayPal", + andere: "Transfer", +}; \ No newline at end of file From a66b150d97800cdfe577b8d3823ae21db6ffc590 Mon Sep 17 00:00:00 2001 From: Theis Date: Wed, 3 Jun 2026 16:29:45 +0200 Subject: [PATCH 2/3] added prize draw name to tables --- .gitignore | 1 + backend/routes/default/frontend.data.js | 18 +++++++----- backend/routes/default/frontend.route.js | 19 ++++++++---- backend/scheme.sql | 9 ++++++ docker-compose.yml | 2 +- .../src/components/modals/SelectUserModal.tsx | 21 +++++++++++--- frontend/src/pages/MainForm.tsx | 29 +++++++++++++++---- frontend/src/utils/api/form.ts | 9 +++++- frontend/src/utils/api/users.ts | 6 ++-- frontend/src/utils/i18n/locales/de/de.json | 3 +- frontend/src/utils/i18n/locales/en/en.json | 3 +- 11 files changed, 91 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index b98371a..eb51ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,7 @@ keys/ # Own files ToDo.txt PayPal-QR-Code.png +.docker/volumes # only in development branch next-env.d.ts \ No newline at end of file diff --git a/backend/routes/default/frontend.data.js b/backend/routes/default/frontend.data.js index 833f8e7..82ea938 100644 --- a/backend/routes/default/frontend.data.js +++ b/backend/routes/default/frontend.data.js @@ -11,14 +11,16 @@ const pool = mysql }) .promise(); -export const getUser = async () => { - const [rows] = await pool.query("SELECT username FROM users"); +export const getInfo = async () => { + const [rows] = await pool.query("SELECT username FROM users;"); + const [rows2] = await pool.query("SELECT name FROM prize_draws;"); - if (rows.length > 0) { + if (rows.length > 0 && rows2.length > 0) { const users = rows.map((r) => r.username); - return { users }; + const prize_draws = rows2.map((n) => n.name); + return { users, prize_draws }; } else { - return { users: [], message: "No data found" }; + return { message: "No data found" }; } }; @@ -43,6 +45,7 @@ export const confirmUser = async (username) => { const [createTable] = await pool.query( `CREATE TABLE IF NOT EXISTS ?? ( id INT AUTO_INCREMENT PRIMARY KEY, + Verlosung VARCHAR(100) NOT NULL, Vorname VARCHAR(100) NOT NULL, Nachname Varchar(100) NOT NULL, EMail Varchar(100) NOT NULL, @@ -80,7 +83,7 @@ export const confirmUser = async (username) => { } }; -export const newEntry = async (formData, username) => { +export const newEntry = async (formData, username, prizeDraw) => { const confirmation = await confirmUser(username); if (!confirmation || !confirmation.success) { @@ -90,9 +93,10 @@ export const newEntry = async (formData, username) => { const tableName = confirmation.tableName; const [result] = await pool.query( - `INSERT INTO ?? (Vorname, Nachname, EMail, Telefonnummer, Lose, Firmenname, Vorname_Geschaeftlich, Nachname_Geschaeftlich, EMail_Geschaeftlich, Telefonnummer_Geschaeftlich, Strasse_Hausnr, Plz_Ort, Zahlungsmethode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + `INSERT INTO ?? (Verlosung, Vorname, Nachname, EMail, Telefonnummer, Lose, Firmenname, Vorname_Geschaeftlich, Nachname_Geschaeftlich, EMail_Geschaeftlich, Telefonnummer_Geschaeftlich, Strasse_Hausnr, Plz_Ort, Zahlungsmethode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ tableName, + prizeDraw, formData.firstName, formData.lastName, formData.email, diff --git a/backend/routes/default/frontend.route.js b/backend/routes/default/frontend.route.js index e380a7d..d90a6e3 100644 --- a/backend/routes/default/frontend.route.js +++ b/backend/routes/default/frontend.route.js @@ -3,24 +3,33 @@ import dotenv from "dotenv"; const router = express.Router(); dotenv.config(); -import { getUser, newEntry, confirmUser } from "./frontend.data.js"; +import { getInfo, newEntry, confirmUser } from "./frontend.data.js"; router.post("/new-entry", async (req, res) => { const username = req.query.username; - const result = await newEntry(req.body, username); + const draw = req.query.draw; + + const result = await newEntry(req.body, username, draw); + if (!result.success) { return res.status(500).json({ message: "Form Data Invalid" }); } res.sendStatus(204); }); -router.get("/users", async (req, res) => { - const users = await getUser(); - res.json(users); +router.get("/info", async (req, res) => { + const info = await getInfo(); + + if (!info) { + return res.status(500).json({ message: "Server error" }); + } + + res.json(info); }); router.get("/confirm-user", async (req, res) => { const username = req.query.username; + if (!username) { return res.status(400).json({ message: "Username is required" }); } diff --git a/backend/scheme.sql b/backend/scheme.sql index 98f6c9b..300b7dd 100644 --- a/backend/scheme.sql +++ b/backend/scheme.sql @@ -1,3 +1,5 @@ +USE ca_lose; + CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100) NOT NULL UNIQUE, @@ -6,9 +8,16 @@ CREATE TABLE users ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +CREATE TABLE prize_draws ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + /* This scheme does not have to be implemented manually. It always will be generated by the backend */ CREATE TABLE xx_DD_MM_YYYY ( id INT AUTO_INCREMENT PRIMARY KEY, + Verlosung VARCHAR(100) NOT NULL, Vorname VARCHAR(100) NOT NULL, Nachname Varchar(100) NOT NULL, EMail Varchar(100) NOT NULL, diff --git a/docker-compose.yml b/docker-compose.yml index 2f86d1f..49cc551 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: MYSQL_DATABASE: ca_lose TZ: Europe/Berlin volumes: - - ca-lose_mysql:/var/lib/mysql + - ./.docker/volumes/ca-lose_mysql:/var/lib/mysql - ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro volumes: diff --git a/frontend/src/components/modals/SelectUserModal.tsx b/frontend/src/components/modals/SelectUserModal.tsx index 6f6cd42..1a86328 100644 --- a/frontend/src/components/modals/SelectUserModal.tsx +++ b/frontend/src/components/modals/SelectUserModal.tsx @@ -10,10 +10,12 @@ import { useTranslation } from "react-i18next"; interface SelectUserModalProps { showSelectUser: boolean; setShowSelectUser: (value: boolean) => void; - usernameData: { users: string[] }; - usernameDataIsLoading: boolean; + info: { users: string[]; prize_draws: string[] }; + infoIsLoading: boolean; selectedUser: string | null; + selectedDraw: string | null; handleUserSelection: (value: string | null) => void; + handleDrawSelection: (value: string | null) => void; } export const SelectUserModal = (props: SelectUserModalProps) => { @@ -25,8 +27,8 @@ export const SelectUserModal = (props: SelectUserModalProps) => { {t("user")} {/* User selection */} props.handleUserSelection(value)} @@ -34,6 +36,17 @@ export const SelectUserModal = (props: SelectUserModalProps) => { variant="soft" sx={{ borderRadius: "10px" }} /> + {/* Prize selection */} + props.handleDrawSelection(value)} + placeholder={t("prize_draws")} + variant="soft" + sx={{ borderRadius: "10px" }} + /> ); diff --git a/frontend/src/pages/MainForm.tsx b/frontend/src/pages/MainForm.tsx index 04c274b..ab40baf 100644 --- a/frontend/src/pages/MainForm.tsx +++ b/frontend/src/pages/MainForm.tsx @@ -21,7 +21,7 @@ import PersonIcon from "@mui/icons-material/Person"; import QrCodeIcon from "@mui/icons-material/QrCode"; import TranslateIcon from "@mui/icons-material/Translate"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { confirmUser, fetchUsers } from "../utils/api/users"; +import { confirmUser, fetchInfo } from "../utils/api/users"; import { QRcodeModal } from "../components/modals/QR-CodeModal"; import { SelectUserModal } from "../components/modals/SelectUserModal"; import { useForm } from "@tanstack/react-form"; @@ -39,6 +39,7 @@ export const MainForm = () => { const [invoice, setInvoice] = useState(false); const [msg, setMsg] = useState(null); const [selectedUser, setSelectedUser] = useState(null); + const [selectedDraw, setSelectedDraw] = useState(null); const [showSelectUser, setShowSelectUser] = useState(false); const [QRmodal, setQRmodal] = useState(false); @@ -106,6 +107,11 @@ export const MainForm = () => { // useEffect that only executes on mount and is checking if there is a user selcted in the cookies useEffect(() => { const savedUser = Cookies.get("selectedUser"); + const savedDraw = Cookies.get("selectedDraw"); + if (savedDraw) { + setSelectedDraw(savedDraw); + } + if (savedUser) { setSelectedUser(savedUser); } else { @@ -118,9 +124,9 @@ export const MainForm = () => { }, []); // Fetch users tanstack query - const { data: usernameData, isLoading: usernameDataIsLoading } = useQuery({ - queryKey: ["users"], - queryFn: fetchUsers, + const { data: info, isLoading: infoIsLoading } = useQuery({ + queryKey: ["info"], + queryFn: fetchInfo, }); // Query for validating selcted user @@ -157,15 +163,26 @@ export const MainForm = () => { setSelectedUser(username); }; + // function for selecting draw + const handleDrawSelection = (draw: string | null) => { + if (draw == null || draw == "") { + return; + } + setSelectedDraw(draw); + Cookies.set("selectedDraw", draw); + }; + return ( <> diff --git a/frontend/src/utils/api/form.ts b/frontend/src/utils/api/form.ts index d74138c..26ed344 100644 --- a/frontend/src/utils/api/form.ts +++ b/frontend/src/utils/api/form.ts @@ -1,5 +1,6 @@ import { API_BASE } from "../../config/api.config"; import type { FormData } from "../../config/interfaces.config"; +import Cookies from "js-cookie"; export const submitFormData = async ( data: FormData, @@ -7,10 +8,16 @@ export const submitFormData = async ( ) => { console.warn("submitFormData is fetching!"); + const draw = Cookies.get("selectedDraw"); + + if (!draw) { + throw new Error("You need to set an prize draw!"); + } + // await new Promise((resolve) => setTimeout(resolve, 3000)); // Wait 3 seconds const response = await fetch( - `${API_BASE}/default/new-entry?username=${username}`, + `${API_BASE}/default/new-entry?username=${username}&draw=${draw}`, { method: "POST", headers: { diff --git a/frontend/src/utils/api/users.ts b/frontend/src/utils/api/users.ts index aac1c23..1902544 100644 --- a/frontend/src/utils/api/users.ts +++ b/frontend/src/utils/api/users.ts @@ -1,10 +1,10 @@ import { API_BASE } from "../../config/api.config"; import Cookies from "js-cookie"; -export const fetchUsers = async () => { - console.warn("fetchUsers is fetching!"); +export const fetchInfo = async () => { + console.warn("fetchInfo is fetching!"); - const response = await fetch(`${API_BASE}/default/users`); + const response = await fetch(`${API_BASE}/default/info`); const data = await response.json(); return data; diff --git a/frontend/src/utils/i18n/locales/de/de.json b/frontend/src/utils/i18n/locales/de/de.json index 605df4c..ceffc37 100644 --- a/frontend/src/utils/i18n/locales/de/de.json +++ b/frontend/src/utils/i18n/locales/de/de.json @@ -14,11 +14,12 @@ "user": "Benutzer", "next-id": "Nächste Eintragsnummer: ", "form-submitted-successfully": "Formular erfolgreich übermittelt!", - "form-submission-failed": "Formularübermittlung fehlgeschlagen.", + "form-submission-failed": "Formularübermittlung fehlgeschlagen. Haben Sie eine Verlosung ausgewählt?", "success": "Erfolg", "error": "Fehler", "cash": "Bar", "paypal": "PayPal", + "prize_draws": "Verlosungen", "transfer": "Andere (notieren)", "ticket-payment_one": "Sie haben erfolgreich {{count}} Los gekauft.", "ticket-payment_other": "Sie haben erfolgreich {{count}} Lose gekauft.", diff --git a/frontend/src/utils/i18n/locales/en/en.json b/frontend/src/utils/i18n/locales/en/en.json index 8c2e961..e2c55b3 100644 --- a/frontend/src/utils/i18n/locales/en/en.json +++ b/frontend/src/utils/i18n/locales/en/en.json @@ -13,7 +13,8 @@ "user": "User", "next-id": "Next Entry Number: ", "form-submitted-successfully": "Form submitted successfully!", - "orm-submission-failed": "Form submission failed.", + "form-submission-failed": "Form submission failed. Have you set an prize draw?", + "prize_draws": "Prize draws", "success": "Success", "error": "Error", "cash": "Cash", From d49e62a28071dfd67dc3d9971eac14ce46f2c0f1 Mon Sep 17 00:00:00 2001 From: Theis Date: Wed, 3 Jun 2026 16:32:19 +0200 Subject: [PATCH 3/3] updated wg easy module --- wg-easy-ca-lose | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wg-easy-ca-lose b/wg-easy-ca-lose index 9581e6e..8c70c24 160000 --- a/wg-easy-ca-lose +++ b/wg-easy-ca-lose @@ -1 +1 @@ -Subproject commit 9581e6eacbbbe02e77486fd139a8153d3704389a +Subproject commit 8c70c242054a6ca66d5cd5cf0a81902672303d23