2 Commits

Author SHA1 Message Date
theis.gaedigk 1f11a4ecab added page footer 2026-05-24 13:50:41 +02:00
theis.gaedigk ccb09caa4f refactored code 2026-05-24 13:26:52 +02:00
10 changed files with 229 additions and 231 deletions
+4
View File
@@ -6,3 +6,7 @@ body,
height: 100%; height: 100%;
margin: 0; margin: 0;
} }
body.success-bg {
background: linear-gradient(135deg, #0f172a, #111827);
}
+12 -6
View File
@@ -2,15 +2,21 @@ import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom"; import { BrowserRouter, Route, Routes } from "react-router-dom";
import { MainForm } from "./pages/MainForm"; import { MainForm } from "./pages/MainForm";
import { SuccessPage } from "./pages/SuccessPage"; import { SuccessPage } from "./pages/SuccessPage";
import { PageFooter } from "./components/PageFooter";
function App() { function App() {
return ( return (
<BrowserRouter> <div className="min-h-screen flex flex-col">
<Routes> <BrowserRouter>
<Route path="/" element={<MainForm />} /> <main className="flex-1 flex">
<Route path="/success" element={<SuccessPage />} /> <Routes>
</Routes> <Route path="/" element={<MainForm />} />
</BrowserRouter> <Route path="/success" element={<SuccessPage />} />
</Routes>
</main>
</BrowserRouter>
<PageFooter />
</div>
); );
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+42
View File
@@ -0,0 +1,42 @@
import { Link, Sheet, Typography } from "@mui/joy";
import { useTranslation } from "react-i18next";
import qrCode from "../assets/Portfolio-QR-Code.png";
export const PageFooter = () => {
const { t } = useTranslation();
return (
<footer className="w-full mt-auto px-3 pb-3">
<Sheet
variant="soft"
className="mx-auto w-full max-w-3xl rounded-2xl border border-slate-200/70 bg-white/80 backdrop-blur"
>
<div className="flex flex-col gap-2 p-2.5 sm:p-3">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<Typography
level="title-sm"
className="text-slate-800 tracking-wide"
>
{t("footer-headline")}
<Link
href="https://portfolio-theis.de/"
target="_blank"
rel="noreferrer"
className="ml-2 inline-flex items-center text-slate-700 underline decoration-slate-300 underline-offset-4 hover:text-slate-900"
>
portfolio-theis.de
</Link>
</Typography>
<Typography level="body-sm" className="text-slate-500">
<img
src={qrCode}
alt="https://portfolio-theis.de/"
className="h-20 w-20 rounded-md border border-slate-200 object-contain"
/>
</Typography>
</div>
</div>
</Sheet>
</footer>
);
};
+31
View File
@@ -0,0 +1,31 @@
import type { TextFieldProps } from "../config/interfaces.config";
import { FormControl, FormLabel, Input } from "@mui/joy";
export const TextField = ({
label,
type = "text",
required,
errors,
value,
onBlur,
onChange,
slotProps,
afterInput,
}: TextFieldProps) => (
<FormControl required={required}>
<FormLabel>{label}</FormLabel>
<Input
value={value ?? ""}
onBlur={onBlur}
onChange={(e) => onChange(e.target.value)}
type={type}
variant="soft"
sx={{ borderRadius: "10px" }}
slotProps={slotProps}
/>
{afterInput}
{errors[0] ? (
<span className="text-red-500 text-sm">{errors[0]}</span>
) : null}
</FormControl>
);
+13
View File
@@ -1,5 +1,6 @@
import z from "zod"; import z from "zod";
import validator from "validator"; import validator from "validator";
import type { ReactNode } from "react";
export interface FormData { export interface FormData {
firstName: string; firstName: string;
@@ -23,6 +24,18 @@ export interface Message {
text: string; text: string;
} }
export type TextFieldProps = {
label: string;
type?: "text" | "email" | "tel" | "number";
required?: boolean;
errors: string[];
onBlur: () => void;
onChange: (value: string) => void;
value: string | number | null | undefined;
slotProps?: { input?: Record<string, unknown> };
afterInput?: ReactNode;
};
export const createFormSchema = ( export const createFormSchema = (
t: (key: string) => string, t: (key: string) => string,
invoice: boolean, invoice: boolean,
+116 -221
View File
@@ -28,6 +28,7 @@ import { useForm } from "@tanstack/react-form";
import { changeTranslation } from "../utils/uxFncs"; import { changeTranslation } from "../utils/uxFncs";
import { createFormSchema } from "../config/interfaces.config"; import { createFormSchema } from "../config/interfaces.config";
import type { ZodObject, ZodRawShape } from "zod"; import type { ZodObject, ZodRawShape } from "zod";
import { TextField } from "../components/TextField";
const PAYMENT_METHODS = ["bar", "paypal", "andere"] as const; const PAYMENT_METHODS = ["bar", "paypal", "andere"] as const;
const PAYMENT_LABELS: Record<string, string> = { const PAYMENT_LABELS: Record<string, string> = {
@@ -183,7 +184,7 @@ export const MainForm = () => {
<QRcodeModal setQRmodal={setQRmodal} QRmodal={QRmodal} /> <QRcodeModal setQRmodal={setQRmodal} QRmodal={QRmodal} />
<div className="min-h-screen w-full flex items-center justify-center from-slate-100 to-blue-50 p-4"> <div className="flex-1 w-full flex items-center justify-center from-slate-100 to-blue-50 p-4">
<Sheet <Sheet
variant="plain" variant="plain"
className="w-full" className="w-full"
@@ -252,100 +253,64 @@ export const MainForm = () => {
name="firstName" name="firstName"
validators={makeFieldValidator("firstName")} validators={makeFieldValidator("firstName")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("first-name")}
<FormControl required> required
<FormLabel>{t("first-name")}</FormLabel> value={field.state.value}
<Input onBlur={field.handleBlur}
value={field.state.value ?? ""} onChange={field.handleChange}
onBlur={field.handleBlur} errors={getErrors(field)}
onChange={(e) => field.handleChange(e.target.value)} />
variant="soft" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
<Field <Field
name="lastName" name="lastName"
validators={makeFieldValidator("lastName")} validators={makeFieldValidator("lastName")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("last-name")}
<FormControl required> required
<FormLabel>{t("last-name")}</FormLabel> value={field.state.value}
<Input onBlur={field.handleBlur}
value={field.state.value ?? ""} onChange={field.handleChange}
onBlur={field.handleBlur} errors={getErrors(field)}
onChange={(e) => field.handleChange(e.target.value)} />
variant="soft" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
</div> </div>
<Field name="email" validators={makeFieldValidator("email")}> <Field name="email" validators={makeFieldValidator("email")}>
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("email")}
<FormControl required> type="email"
<FormLabel>{t("email")}</FormLabel> required
<Input value={field.state.value}
value={field.state.value ?? ""} onBlur={field.handleBlur}
onBlur={field.handleBlur} onChange={field.handleChange}
onChange={(e) => field.handleChange(e.target.value)} errors={getErrors(field)}
variant="soft" />
type="email" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">{errors[0]}</span>
)}
</FormControl>
);
}}
</Field> </Field>
<Field <Field
name="phoneNumber" name="phoneNumber"
validators={makeFieldValidator("phoneNumber")} validators={makeFieldValidator("phoneNumber")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("phone-number")}
<FormControl required> type="tel"
<FormLabel>{t("phone-number")}</FormLabel> required
<Input value={field.state.value}
value={field.state.value ?? ""} onBlur={field.handleBlur}
onBlur={field.handleBlur} onChange={field.handleChange}
onChange={(e) => field.handleChange(e.target.value)} errors={getErrors(field)}
variant="soft" />
type="tel" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">{errors[0]}</span>
)}
</FormControl>
);
}}
</Field> </Field>
{/* Tickets + Invoice toggle */} {/* Tickets + Invoice toggle */}
@@ -414,26 +379,16 @@ export const MainForm = () => {
name="companyName" name="companyName"
validators={makeFieldValidator("companyName")} validators={makeFieldValidator("companyName")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("company-name")}
<FormControl required> required
<FormLabel>{t("company-name")}</FormLabel> value={field.state.value}
<Input onBlur={field.handleBlur}
value={field.state.value ?? ""} onChange={field.handleChange}
onBlur={field.handleBlur} errors={getErrors(field)}
onChange={(e) => field.handleChange(e.target.value)} />
variant="soft" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
@@ -441,156 +396,96 @@ export const MainForm = () => {
name="cmpFirstName" name="cmpFirstName"
validators={makeFieldValidator("cmpFirstName")} validators={makeFieldValidator("cmpFirstName")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("first-name")}
<FormControl required> required
<FormLabel>{t("first-name")}</FormLabel> value={field.state.value}
<Input onBlur={field.handleBlur}
value={field.state.value ?? ""} onChange={field.handleChange}
onBlur={field.handleBlur} errors={getErrors(field)}
onChange={(e) => field.handleChange(e.target.value)} />
variant="soft" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
<Field <Field
name="cpmLastName" name="cpmLastName"
validators={makeFieldValidator("cpmLastName")} validators={makeFieldValidator("cpmLastName")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("last-name")}
<FormControl required> required
<FormLabel>{t("last-name")}</FormLabel> value={field.state.value}
<Input onBlur={field.handleBlur}
value={field.state.value ?? ""} onChange={field.handleChange}
onBlur={field.handleBlur} errors={getErrors(field)}
onChange={(e) => field.handleChange(e.target.value)} />
variant="soft" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
</div> </div>
<Field name="street" validators={makeFieldValidator("street")}> <Field name="street" validators={makeFieldValidator("street")}>
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("street")}
<FormControl required> required
<FormLabel>{t("street")}</FormLabel> value={field.state.value}
<Input onBlur={field.handleBlur}
value={field.state.value ?? ""} onChange={field.handleChange}
onBlur={field.handleBlur} errors={getErrors(field)}
onChange={(e) => field.handleChange(e.target.value)} />
variant="soft" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
<Field <Field
name="postalCode" name="postalCode"
validators={makeFieldValidator("postalCode")} validators={makeFieldValidator("postalCode")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("postal-code")}
<FormControl required> required
<FormLabel>{t("postal-code")}</FormLabel> value={field.state.value}
<Input onBlur={field.handleBlur}
value={field.state.value ?? ""} onChange={field.handleChange}
onBlur={field.handleBlur} errors={getErrors(field)}
onChange={(e) => field.handleChange(e.target.value)} />
variant="soft" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
<Field <Field
name="cpmPhoneNumber" name="cpmPhoneNumber"
validators={makeFieldValidator("cpmPhoneNumber")} validators={makeFieldValidator("cpmPhoneNumber")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("phone-number")}
<FormControl required> type="tel"
<FormLabel>{t("phone-number")}</FormLabel> required
<Input value={field.state.value}
value={field.state.value ?? ""} onBlur={field.handleBlur}
onBlur={field.handleBlur} onChange={field.handleChange}
onChange={(e) => field.handleChange(e.target.value)} errors={getErrors(field)}
variant="soft" />
type="tel" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
<Field <Field
name="cpmEmail" name="cpmEmail"
validators={makeFieldValidator("cpmEmail")} validators={makeFieldValidator("cpmEmail")}
> >
{(field) => { {(field) => (
const errors = getErrors(field); <TextField
return ( label={t("email")}
<FormControl required> type="email"
<FormLabel>{t("email")}</FormLabel> required
<Input value={field.state.value}
value={field.state.value ?? ""} onBlur={field.handleBlur}
onBlur={field.handleBlur} onChange={field.handleChange}
onChange={(e) => field.handleChange(e.target.value)} errors={getErrors(field)}
variant="soft" />
type="email" )}
sx={{ borderRadius: "10px" }}
/>
{errors.length > 0 && (
<span className="text-red-500 text-sm">
{errors[0]}
</span>
)}
</FormControl>
);
}}
</Field> </Field>
</div> </div>
)} )}
+6 -1
View File
@@ -16,6 +16,11 @@ export const SuccessPage = () => {
setTickets(parseInt(params.get("tickets") ?? "0", 10)); setTickets(parseInt(params.get("tickets") ?? "0", 10));
// Small delay so the CSS transition actually plays // Small delay so the CSS transition actually plays
setTimeout(() => setAnimate(true), 100); setTimeout(() => setAnimate(true), 100);
document.body.classList.add("success-bg");
return () => {
document.body.classList.remove("success-bg");
};
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -36,7 +41,7 @@ export const SuccessPage = () => {
}); });
return ( return (
<div className="min-h-screen w-full flex items-center justify-center bg-linear-to-br from-slate-800 to-slate-900 p-4"> <div className="flex-1 w-full flex items-center justify-center p-4">
<Sheet <Sheet
variant="plain" variant="plain"
sx={{ sx={{
+2 -1
View File
@@ -33,5 +33,6 @@
"set-username-text": "Um mit dem Losverkauf zu beginnen, musst du einen Benutzer oben links auswählen.", "set-username-text": "Um mit dem Losverkauf zu beginnen, musst du einen Benutzer oben links auswählen.",
"name-error": "Sie müssen einen Namen eingeben!", "name-error": "Sie müssen einen Namen eingeben!",
"email-error": "Sie müssen eine gültige E-Mail Adresse eingeben!", "email-error": "Sie müssen eine gültige E-Mail Adresse eingeben!",
"phone-error": "Sie müssen eine gültige Telefonnummer eingeben!" "phone-error": "Sie müssen eine gültige Telefonnummer eingeben!",
"footer-headline": "Dieses System wurde vollständig konzipiert und entwickelt von Theis Gaedigk. - Portfolio: "
} }
+3 -2
View File
@@ -33,6 +33,7 @@
"set-username-headline": "No user selected", "set-username-headline": "No user selected",
"set-username-text": "To start the ticket sale, you must select a user first from the top left.", "set-username-text": "To start the ticket sale, you must select a user first from the top left.",
"name-error": "You have to enter a name!", "name-error": "You have to enter a name!",
"email-error": "You have to enter a valid e-mail Adress!", "email-error": "You have to enter a valid E-Mail adress!",
"phone-error": "You have to enter a vaild phone number!" "phone-error": "You have to enter a vaild phone number!",
"footer-headline": "This system was fully designed and developed by Theis Gaedigk. - Portfolio: "
} }