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/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 36a5df2..ab40baf 100644
--- a/frontend/src/pages/MainForm.tsx
+++ b/frontend/src/pages/MainForm.tsx
@@ -21,51 +21,31 @@ 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";
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);
+ const [selectedDraw, setSelectedDraw] = useState(null);
const [showSelectUser, setShowSelectUser] = useState(false);
const [QRmodal, setQRmodal] = useState(false);
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 +65,7 @@ export const MainForm = () => {
},
});
+ // Creates a form with tanstack form
const { Field, Subscribe, handleSubmit, setFieldValue } = useForm({
defaultValues: {
firstName: "",
@@ -108,6 +89,7 @@ export const MainForm = () => {
},
});
+ // This function returns the errors for one field
const getErrors = (field: {
state: { meta: { errorMap: Record } };
}) => {
@@ -122,8 +104,14 @@ 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");
+ const savedDraw = Cookies.get("selectedDraw");
+ if (savedDraw) {
+ setSelectedDraw(savedDraw);
+ }
+
if (savedUser) {
setSelectedUser(savedUser);
} else {
@@ -135,17 +123,20 @@ export const MainForm = () => {
}
}, []);
- const { data: usernameData, isLoading: usernameDataIsLoading } = useQuery({
- queryKey: ["users"],
- queryFn: fetchUsers,
+ // Fetch users tanstack query
+ const { data: info, isLoading: infoIsLoading } = useQuery({
+ queryKey: ["info"],
+ queryFn: fetchInfo,
});
+ // 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 +155,7 @@ export const MainForm = () => {
const nextID = userData?.nextID ?? "N/A";
+ // function for selecting user
const handleUserSelection = (username: string | null) => {
if (username == null || username == "") {
return;
@@ -171,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",
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
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