Server-renderet af FastAPI selv (Jinja2 + vanilla JS/CSS, intet build-step) mod det eksisterende domæneopløste JSON-API fra 8b. GET / viser bestillingssiden når en runde er åben (rundeoverskrift, intro_text, kategori-grupperet vinkatalog med live-beregnet EUR/DKK-total, bestillingsformular), ellers en tilmeldingsside. GET /tilmelding er altid tilgængelig uafhængigt af rundestatus, og GET /afhentning viser rutens meeting_info. Layoutet er modelleret efter den gamle sides bestillingsside. get_current_round's serialiseringslogik er udtrukket til en delt _build_page_info-helper, så JSON-API'et og de nye HTML-sider genbruger nøjagtig samme domæne-/rundeopslag. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
127 lines
4.4 KiB
JavaScript
127 lines
4.4 KiB
JavaScript
(function () {
|
|
function formatAmount(n) {
|
|
return n.toFixed(2);
|
|
}
|
|
|
|
function showMessage(el, text, kind) {
|
|
el.textContent = text;
|
|
el.className = "form-message " + kind;
|
|
el.hidden = false;
|
|
}
|
|
|
|
async function extractErrorDetail(response) {
|
|
try {
|
|
const body = await response.json();
|
|
if (typeof body.detail === "string") return body.detail;
|
|
if (Array.isArray(body.detail)) {
|
|
return body.detail.map((e) => e.msg || JSON.stringify(e)).join(", ");
|
|
}
|
|
return JSON.stringify(body.detail);
|
|
} catch (err) {
|
|
return "Der skete en fejl. Prøv igen.";
|
|
}
|
|
}
|
|
|
|
const orderForm = document.getElementById("order-form");
|
|
if (orderForm) {
|
|
const qtyInputs = Array.from(orderForm.querySelectorAll(".qty-input"));
|
|
const totalEurEl = document.getElementById("total-eur");
|
|
const totalDkkEl = document.getElementById("total-dkk");
|
|
const rateAttr = orderForm.dataset.eurDkkRate;
|
|
const rate = rateAttr ? parseFloat(rateAttr) : null;
|
|
|
|
function recalculate() {
|
|
let totalEur = 0;
|
|
qtyInputs.forEach((input) => {
|
|
const qty = parseInt(input.value, 10) || 0;
|
|
const price = parseFloat(input.dataset.price) || 0;
|
|
const rowTotal = qty * price;
|
|
totalEur += rowTotal;
|
|
const cell = orderForm.querySelector(
|
|
'[data-row-total-for="' + input.dataset.offeringId + '"]'
|
|
);
|
|
if (cell) cell.textContent = formatAmount(rowTotal);
|
|
});
|
|
if (totalEurEl) totalEurEl.textContent = formatAmount(totalEur);
|
|
if (totalDkkEl && rate) totalDkkEl.textContent = formatAmount(totalEur * rate);
|
|
}
|
|
|
|
qtyInputs.forEach((input) => input.addEventListener("input", recalculate));
|
|
recalculate();
|
|
|
|
orderForm.addEventListener("submit", async function (event) {
|
|
event.preventDefault();
|
|
const messageEl = document.getElementById("order-message");
|
|
const orderLines = qtyInputs
|
|
.map((input) => ({
|
|
wine_offering_id: parseInt(input.dataset.offeringId, 10),
|
|
quantity: parseInt(input.value, 10) || 0,
|
|
}))
|
|
.filter((line) => line.quantity > 0);
|
|
|
|
if (orderLines.length === 0) {
|
|
showMessage(messageEl, "Vælg mindst én vin med et antal større end 0.", "error");
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
participant: {
|
|
name: orderForm.elements["name"].value,
|
|
email: orderForm.elements["email"].value,
|
|
phone: orderForm.elements["phone"].value,
|
|
is_active: orderForm.elements["is_active"].checked,
|
|
},
|
|
order_lines: orderLines,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch("/public/orders", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (response.ok) {
|
|
orderForm.querySelector(".catalog").hidden = true;
|
|
orderForm.querySelectorAll(".field-label, .checkbox-label, button[type=submit]").forEach((el) => {
|
|
el.hidden = true;
|
|
});
|
|
showMessage(messageEl, "Tak for din bestilling! En kvittering er sendt til din email.", "success");
|
|
} else {
|
|
showMessage(messageEl, await extractErrorDetail(response), "error");
|
|
}
|
|
} catch (err) {
|
|
showMessage(messageEl, "Kunne ikke sende bestillingen. Tjek din internetforbindelse og prøv igen.", "error");
|
|
}
|
|
});
|
|
}
|
|
|
|
const signupForm = document.getElementById("signup-form");
|
|
if (signupForm) {
|
|
signupForm.addEventListener("submit", async function (event) {
|
|
event.preventDefault();
|
|
const messageEl = document.getElementById("signup-message");
|
|
const payload = {
|
|
name: signupForm.elements["name"].value,
|
|
email: signupForm.elements["email"].value,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch("/public/signup", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (response.ok) {
|
|
signupForm.querySelectorAll("label, button[type=submit]").forEach((el) => {
|
|
el.hidden = true;
|
|
});
|
|
showMessage(messageEl, "Tak! Du er nu tilmeldt.", "success");
|
|
} else {
|
|
showMessage(messageEl, await extractErrorDetail(response), "error");
|
|
}
|
|
} catch (err) {
|
|
showMessage(messageEl, "Kunne ikke sende tilmeldingen. Tjek din internetforbindelse og prøv igen.", "error");
|
|
}
|
|
});
|
|
}
|
|
})();
|