43 Commits

Author SHA1 Message Date
theis.gaedigk 43f8e00968 Merge branch 'dev' into prod 2026-05-20 10:39:43 +02:00
theis.gaedigk f32931ded3 added wg-easy 2026-05-20 10:38:25 +02:00
theis.gaedigk 4ce1817bd0 added wg-easy 2026-05-20 10:37:31 +02:00
theis.gaedigk 11c2372cae outsourced modals 2026-05-19 21:54:02 +02:00
theis.gaedigk d5b6c9665c added new warning 2026-05-18 23:23:43 +02:00
theis.gaedigk ce2d0bb329 noted out 3sec pause 2026-05-18 20:41:35 +02:00
theis.gaedigk 5c43b817a7 Merge branch 'dev' into prod 2026-05-18 20:37:04 +02:00
theis.gaedigk 0953847f24 Merge branch 'dev' into prod 2026-05-10 21:08:23 +02:00
theis.gaedigk 9e7fc530b5 Merge branch 'dev' into prod 2026-05-10 21:05:13 +02:00
theis.gaedigk 18777e5f7c added allowed ips 2026-05-09 23:10:47 +02:00
theis.gaedigk b55129dfff closed ports 2026-05-09 23:06:17 +02:00
theis.gaedigk 26856ee1df edited 2026-05-09 23:03:56 +02:00
theis.gaedigk dd1d8d8d6b edited network config 2026-05-09 21:52:39 +02:00
theis.gaedigk cf2df0aaac edited code design 2026-05-09 21:46:01 +02:00
theis.gaedigk 1199d6468f noted out public web-ui port 2026-05-09 21:45:19 +02:00
theis.gaedigk 7cd958c31e edited ip adresses 2026-05-09 21:43:12 +02:00
theis.gaedigk f89cf84a38 edited docker config 2026-05-09 21:38:56 +02:00
theis.gaedigk e3fc1d8659 edited again 2026-05-09 21:30:59 +02:00
theis.gaedigk 060f8d01c6 edited again 2026-05-09 21:27:47 +02:00
theis.gaedigk 667609d70c fixed docker config 2026-05-09 21:24:42 +02:00
theis.gaedigk b05f19acd9 edited docker compose 2026-05-09 21:22:28 +02:00
theis.gaedigk 2aa9a968f5 Merge branch 'dev' into prod 2026-05-04 22:40:33 +02:00
theis.gaedigk e42a2f510a edited docker compose 2026-05-04 22:05:18 +02:00
theis.gaedigk d2b22fc71f Merge branch 'dev' into prod 2026-05-04 22:04:26 +02:00
theis.gaedigk 471c0c7a49 Merge branch 'dev' into prod 2026-01-21 16:33:03 +01:00
theis.gaedigk 75ff65e76b Merge branch 'dev' into prod 2026-01-21 16:28:23 +01:00
theis.gaedigk 7cf1245ef6 Merge branch 'dev' into prod 2026-01-21 14:27:37 +01:00
theis.gaedigk 2adbfa75a5 Merge branch 'dev' into prod 2026-01-21 14:07:26 +01:00
theis.gaedigk 216a1cb1d4 Merge branch 'dev' into prod 2026-01-20 20:43:59 +01:00
theis.gaedigk 7fc98d6c9f Merge branch 'dev' into prod 2026-01-20 20:34:52 +01:00
theis.gaedigk e346cf9445 e 2026-01-20 20:33:41 +01:00
theis.gaedigk c030b6dbe6 Merge branch 'dev' into prod 2026-01-20 20:33:31 +01:00
theis.gaedigk 6f26b9bbc3 e 2026-01-20 20:22:59 +01:00
theis.gaedigk a34a70572f edited 2026-01-20 20:19:12 +01:00
theis.gaedigk 4b3c8a2424 edited compose file 2026-01-20 20:17:53 +01:00
theis.gaedigk 568b3bf495 edited 2026-01-20 20:08:14 +01:00
theis.gaedigk 5653d32857 fix: update WireGuard PASSWORD_HASH to a static value 2026-01-20 20:06:44 +01:00
theis.gaedigk 7cf5b8df48 Merge branch 'dev' into prod 2026-01-20 20:03:51 +01:00
theis.gaedigk 65c5fc0f8f Merge branch 'dev' into prod 2026-01-20 19:59:27 +01:00
theis.gaedigk b626a67907 Merge branch 'dev' into prod 2026-01-20 19:46:56 +01:00
theis.gaedigk 6643a176a6 Merge branch 'dev' into prod 2026-01-20 19:43:53 +01:00
theis.gaedigk 89803754a7 Merge branch 'dev' into prod 2026-01-20 19:38:27 +01:00
theis.gaedigk 5052b3e83a changed fetch urls 2026-01-20 19:23:53 +01:00
10 changed files with 209 additions and 91 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "wg-easy-ca-lose"]
path = wg-easy-ca-lose
url = https://git.the1s.de/theis.gaedigk/wg-easy-ca-lose.git
+75 -12
View File
@@ -1,16 +1,19 @@
services:
# frontend:
# container_name: ca-lose-frontend
# build: ./frontend
# ports:
# - "8002:80"
# restart: unless-stopped
frontend:
container_name: ca-lose-frontend
hostname: lose-verkaufen
build: ./frontend
depends_on:
- backend
networks:
ca-lose-internal:
ipv4_address: 172.25.0.2
restart: unless-stopped
backend:
container_name: ca-lose-backend
hostname: backend
build: ./backend
ports:
- "8004:8004"
environment:
NODE_ENV: production
DB_HOST: ca-lose-mysql
@@ -19,21 +22,81 @@ services:
DB_NAME: ca_lose
depends_on:
- database
networks:
ca-lose-internal:
ipv4_address: 172.25.0.3
restart: unless-stopped
database:
container_name: ca-lose-mysql
hostname: database
image: mysql:8.0
restart: unless-stopped
ports:
- "3311:3306"
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
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
networks:
ca-lose-internal:
ipv4_address: 172.25.0.4
# DNS Server für Hostname-Auflösung innerhalb des VPN
dnsmasq:
container_name: ca-lose-dns
image: andyshinn/dnsmasq:latest
restart: unless-stopped
cap_add:
- NET_ADMIN
command: >
--no-daemon
--log-queries
--address=/lose-verkaufen/172.25.0.2
--address=/frontend/172.25.0.2
--address=/backend/172.25.0.3
--address=/database/172.25.0.4
--address=/wireguard/172.25.0.6
networks:
ca-lose-internal:
ipv4_address: 172.25.0.5
# WireGuard VPN mit Web-UI (wg-easy)
wireguard:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: ca-lose-wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
LANG: de
WG_HOST: dus3.the1s.de
WG_PORT: "51830"
WG_DEFAULT_DNS: "172.25.0.5"
WG_ALLOWED_IPS: 172.25.0.0/24
PORT: "80" # Web-UI Port
PASSWORD_HASH: ${WIREGUARD_PASSWORD_HASH}
volumes:
ca-lose_mysql:
- ../docker/volumes/ca-lose-wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51830:51830/udp"
# - "51831:80/tcp" # only for short configuration access - remove in production - external: 51831 internal: 80
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
depends_on:
- dnsmasq
networks:
ca-lose-internal:
ipv4_address: 172.25.0.6
networks:
ca-lose-internal:
driver: bridge
ipam:
config:
- subnet: 172.25.0.0/24
gateway: 172.25.0.1
@@ -0,0 +1,31 @@
import { Modal, ModalDialog, Typography, ModalClose } from "@mui/joy";
import { useTranslation } from "react-i18next";
import qrCode from "../../assets/PayPal-QR-Code.png";
interface QRcodeModalProps {
QRmodal: boolean;
setQRmodal: (value: boolean) => void;
}
export const QRcodeModal = (props: QRcodeModalProps) => {
const { t } = useTranslation();
return (
<Modal open={props.QRmodal}>
<ModalDialog color="primary" layout="center" size="lg">
<ModalClose onClick={() => props.setQRmodal(false)} />
<Typography>{t("qr-text")}</Typography>
<img
src={qrCode}
alt="PayPal QR Code"
style={{
width: "100%",
height: "auto",
maxHeight: "70vh",
objectFit: "contain",
}}
/>
</ModalDialog>
</Modal>
);
};
@@ -0,0 +1,40 @@
import {
Modal,
ModalDialog,
Typography,
ModalClose,
Autocomplete,
} from "@mui/joy";
import { useTranslation } from "react-i18next";
interface SelectUserModalProps {
showSelectUser: boolean;
setShowSelectUser: (value: boolean) => void;
usernameData: { users: string[] };
usernameDataIsLoading: boolean;
selectedUser: string | null;
handleUserSelection: (value: string | null) => void;
}
export const SelectUserModal = (props: SelectUserModalProps) => {
const { t } = useTranslation();
return (
<Modal open={props.showSelectUser}>
<ModalDialog color="primary" layout="center" size="lg">
<ModalClose onClick={() => props.setShowSelectUser(false)} />
<Typography>{t("user")}</Typography>
{/* User selection */}
<Autocomplete
options={props.usernameData?.users ?? []}
loading={props.usernameDataIsLoading}
loadingText={t("loading")}
value={props.selectedUser}
onChange={(_, value) => props.handleUserSelection(value)}
placeholder={t("user")}
variant="soft"
sx={{ borderRadius: "10px" }}
/>
</ModalDialog>
</Modal>
);
};
+40 -71
View File
@@ -13,11 +13,7 @@ import {
Typography,
FormControl,
FormLabel,
Autocomplete,
ButtonGroup,
Modal,
ModalDialog,
ModalClose,
CircularProgress,
} from "@mui/joy";
import { submitFormData } from "../utils/api/form";
@@ -25,9 +21,10 @@ 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";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { confirmUser, fetchUsers } from "../utils/api/users";
import { QRcodeModal } from "../components/modals/QR-CodeModal";
import { SelectUserModal } from "../components/modals/SelectUserModal";
const PAYMENT_METHODS = ["bar", "paypal", "andere"] as const;
const PAYMENT_LABELS: Record<string, string> = {
@@ -88,8 +85,7 @@ export const MainForm = () => {
const [invoice, setInvoice] = useState(false);
const [msg, setMsg] = useState<Message | null>(null);
const [nextID, setNextID] = useState<number | null>(null);
const [selectedUser, setSelectedUser] = useState("");
const [selectedUser, setSelectedUser] = useState<string | null>(null);
const [formData, setFormData] = useState<FormData>(DEFAULT_FORM);
const [showSelectUser, setShowSelectUser] = useState(false);
const [QRmodal, setQRmodal] = useState(false);
@@ -102,6 +98,12 @@ export const MainForm = () => {
const savedUser = Cookies.get("selectedUser");
if (savedUser) {
setSelectedUser(savedUser);
} else {
setMsg({
type: "warning",
headline: t("set-username-headline"),
text: t("set-username-text"),
});
}
}, []);
@@ -110,43 +112,30 @@ export const MainForm = () => {
queryFn: fetchUsers,
});
const { data: userData, isSuccess: userDataIsSuccess } = useQuery({
const { data: userData } = useQuery({
queryKey: ["user", selectedUser],
enabled: !!selectedUser,
queryFn: () => confirmUser(selectedUser),
});
const {
mutate: mutateForm,
isSuccess: mutateFormIsSuccess,
isPending: mutateFormIsPending,
isError: mutateFormIsError,
} = useMutation({
const { mutate: mutateForm, isPending: mutateFormIsPending } = useMutation({
mutationFn: () => submitFormData(formData, selectedUser),
});
// Redirecting to success page if mutation was successful
useEffect(() => {
if (mutateFormIsSuccess) {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user", selectedUser] });
document.location.href = `/success?id=${nextID}&tickets=${formData.tickets}`;
}
if (mutateFormIsError) {
},
onError: () => {
queryClient.invalidateQueries({ queryKey: ["user", selectedUser] });
setMsg({
type: "danger",
headline: t("error"),
text: t("form-submission-failed"),
});
}
}, [mutateFormIsSuccess, mutateFormIsError]);
},
});
// Setting the nextID after a user is selected
useEffect(() => {
if (!userData) return;
setNextID(userData.nextID);
}, [userDataIsSuccess]);
const nextID = userData?.nextID ?? "N/A";
const handleUserSelection = (username: string | null) => {
if (username == null || username == "") {
@@ -174,50 +163,21 @@ export const MainForm = () => {
}
};
useEffect(() => {
if (formData.paymentMethod === "paypal") {
setQRmodal(true);
}
}, [formData.paymentMethod]);
// Shorthand so we don't repeat formData + onChange on every Field usage
const fieldProps = { formData, onChange: handleChange };
return (
<>
<Modal open={showSelectUser}>
<ModalDialog color="primary" layout="center" size="lg">
<ModalClose onClick={() => setShowSelectUser(false)} />
<Typography>{t("user")}</Typography>
{/* User selection */}
<Autocomplete
options={usernameData?.users ?? []}
loading={usernameDataIsLoading}
loadingText={t("loading")}
value={selectedUser}
onChange={(_, value) => handleUserSelection(value)}
placeholder={t("user")}
variant="soft"
sx={{ borderRadius: "10px" }}
<SelectUserModal
showSelectUser={showSelectUser}
setShowSelectUser={setShowSelectUser}
usernameData={usernameData}
usernameDataIsLoading={usernameDataIsLoading}
selectedUser={selectedUser}
handleUserSelection={handleUserSelection}
/>
</ModalDialog>
</Modal>
<Modal open={QRmodal}>
<ModalDialog color="primary" layout="center" size="lg">
<ModalClose onClick={() => setQRmodal(false)} />
<Typography>{t("qr-text")}</Typography>
<img
src={qrCode}
alt="PayPal QR Code"
style={{
width: "100%",
height: "auto",
maxHeight: "70vh",
objectFit: "contain",
}}
/>
</ModalDialog>
</Modal>
<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">
<Sheet
@@ -382,12 +342,15 @@ export const MainForm = () => {
formData.paymentMethod === method ? "solid" : "soft"
}
color="primary"
onClick={() =>
onClick={() => {
setFormData((prev) => ({
...prev,
paymentMethod: method,
}))
}));
if (method === "paypal") {
setQRmodal(true);
}
}}
sx={{
flex: 1,
minWidth: "90px",
@@ -442,10 +405,16 @@ export const MainForm = () => {
</Button>
)}
{/* Alert message */}
{/* Message */}
{msg && (
<Alert color={msg.type} sx={{ borderRadius: "12px" }}>
<strong>{msg.headline}:</strong> {msg.text}
<Alert
color={msg.type}
sx={{ flexDirection: "column", alignItems: "flex-start" }}
>
<Typography level="title-lg" sx={{ mb: 0.5 }}>
{msg.headline}
</Typography>
<Typography level="body-sm">{msg.text}</Typography>
</Alert>
)}
</form>
+5 -2
View File
@@ -1,10 +1,13 @@
import { API_BASE } from "../../config/api.config";
import type { FormData } from "../../config/interfaces.config";
export const submitFormData = async (data: FormData, username: string) => {
export const submitFormData = async (
data: FormData,
username: string | null,
) => {
console.warn("submitFormData is fetching!");
await new Promise((resolve) => setTimeout(resolve, 3000)); // Wait 3 seconds
// await new Promise((resolve) => setTimeout(resolve, 3000)); // Wait 3 seconds
const response = await fetch(
`${API_BASE}/default/new-entry?username=${username}`,
+5 -1
View File
@@ -10,7 +10,11 @@ export const fetchUsers = async () => {
return data;
};
export const confirmUser = async (username: string) => {
export const confirmUser = async (username: string | null) => {
if (!username) {
return;
}
console.warn("confirmUser is fetching!");
const response = await fetch(
`${API_BASE}/default/confirm-user?username=${username}`,
+3 -1
View File
@@ -28,5 +28,7 @@
"return-to-homepage": "Zurück",
"qr-text": "PayPal QR-Code der Claudius Akademie",
"loading": "Lädt...",
"greeting": "Hallo,"
"greeting": "Hallo,",
"set-username-headline": "Keinen Benutzer ausgewählt",
"set-username-text": "Um mit dem Losverkauf zu beginnen, musst du einen Benutzer oben links auswählen."
}
+3 -1
View File
@@ -29,5 +29,7 @@
"return-to-homepage": "Return",
"qr-text": "PayPal QR-Code from the Claudius Akademie",
"loading": "Loading...",
"greeting": "Hello,"
"greeting": "Hello,",
"set-username-headline": "No user selected",
"set-username-text": "To start the ticket sale, you must select a user first from the top left."
}
+1
Submodule wg-easy-ca-lose added at da90d67cc0