vinindkoeb/app/static/site.js
carsten 609c3efb05 Opgave 8c: ny offentlig bestillings-/tilmeldingsside
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>
2026-09-28 22:55:55 +02:00

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");
}
});
}
})();