diff --git a/app.js b/app.js index f85d6a0..86c51d1 100644 --- a/app.js +++ b/app.js @@ -151,10 +151,10 @@ app.get("/api/building/:id", requireLogin, async (req, res) => { upgradeCost: nextLevel[0] ? `${nextLevel[0].wood} Holz, ${nextLevel[0].stone} Stein, ${nextLevel[0].gold} Gold` : "Max Level erreicht", - upgradeWood: nextLevel[0]?.wood ?? null, - upgradeStone: nextLevel[0]?.stone ?? null, - upgradeGold: nextLevel[0]?.gold ?? null, - upgradeRequiredPoints: nextLevel[0]?.required_points ?? null, // NEU + upgradeWood: nextLevel[0]?.wood ?? null, + upgradeStone: nextLevel[0]?.stone ?? null, + upgradeGold: nextLevel[0]?.gold ?? null, + upgradeRequiredPoints: nextLevel[0]?.required_points ?? null, // NEU }); } catch (err) { console.error(err); @@ -190,7 +190,9 @@ app.post("/api/building/:id/upgrade", requireLogin, async (req, res) => { ); if (!levelData) { - return res.status(400).json({ error: "Maximales Level bereits erreicht" }); + return res + .status(400) + .json({ error: "Maximales Level bereits erreicht" }); } // Punkte prüfen @@ -210,11 +212,23 @@ app.post("/api/building/:id/upgrade", requireLogin, async (req, res) => { return res.status(400).json({ error: "Keine Währungsdaten gefunden" }); } - if (currency.wood < levelData.wood || currency.stone < levelData.stone || currency.gold < levelData.gold) { + if ( + currency.wood < levelData.wood || + currency.stone < levelData.stone || + currency.gold < levelData.gold + ) { return res.status(400).json({ error: "Nicht genügend Ressourcen", - required: { wood: levelData.wood, stone: levelData.stone, gold: levelData.gold }, - current: { wood: currency.wood, stone: currency.stone, gold: currency.gold }, + required: { + wood: levelData.wood, + stone: levelData.stone, + gold: levelData.gold, + }, + current: { + wood: currency.wood, + stone: currency.stone, + gold: currency.gold, + }, }); } @@ -233,7 +247,11 @@ app.post("/api/building/:id/upgrade", requireLogin, async (req, res) => { res.json({ success: true, newLevel: nextLevel, - cost: { wood: levelData.wood, stone: levelData.stone, gold: levelData.gold }, + cost: { + wood: levelData.wood, + stone: levelData.stone, + gold: levelData.gold, + }, }); } catch (err) { console.error(err); @@ -297,6 +315,42 @@ app.get("/api/buildings", requireLogin, async (req, res) => { } }); +/* ======================== + Cards API +======================== */ + +app.get("/api/cards", requireLogin, async (req, res) => { + const { race, page = 1, limit = 12 } = req.query; + const offset = (page - 1) * limit; + + try { + const [cards] = await db.query( + `SELECT c.*, cg.name AS group_name, cg.color AS group_color, + cl.attack, cl.defense, cl.cooldown + FROM cards c + LEFT JOIN card_groups cg ON cg.id = c.group_id + LEFT JOIN card_levels cl ON cl.card_id = c.id AND cl.level = 1 + WHERE c.race = ? + LIMIT ? OFFSET ?`, + [race, parseInt(limit), parseInt(offset)], + ); + const [[{ total }]] = await db.query( + "SELECT COUNT(*) as total FROM cards WHERE race = ?", + [race], + ); + + res.json({ + cards, + total, + page: parseInt(page), + totalPages: Math.ceil(total / limit), + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + /* ======================== Body Parser ======================== */ diff --git a/public/images/playcards/Silberklinge.png b/public/images/cards/Silberklinge.png similarity index 100% rename from public/images/playcards/Silberklinge.png rename to public/images/cards/Silberklinge.png diff --git a/public/js/lucky-box.js b/public/js/lucky-box.js new file mode 100644 index 0000000..deb84f8 --- /dev/null +++ b/public/js/lucky-box.js @@ -0,0 +1,337 @@ +const CARDS_PER_PAGE = 12; + +const RACES = [ + { id: "menschen", label: "Menschen", icon: "⚔️" }, + { id: "elfen", label: "Elfen", icon: "🌿" }, + { id: "zwerge", label: "Zwerge", icon: "⛏️" }, + { id: "orks", label: "Orks", icon: "💀" }, + { id: "untote", label: "Untote", icon: "💀" }, + { id: "drachen", label: "Drachen", icon: "🐉" }, +]; + +let currentRace = RACES[0].id; +let currentPage = 1; + +export async function loadLuckyBox() { + const body = document.getElementById("qm-body-glucksbox"); + if (!body) return; + + body.innerHTML = renderShell(); + attachTabEvents(); + await loadCards(); +} + +/* ── HTML-Grundstruktur ─────────────────────── */ +function renderShell() { + const tabs = RACES.map((r, i) => ` + + `).join(""); + + return ` +
+ +
+
+
+
+
+ + + `; +} + +/* ── Tab-Klick ─────────────────────────────── */ +function attachTabEvents() { + document.querySelectorAll(".kd-tab").forEach((btn) => { + btn.addEventListener("click", async () => { + document.querySelectorAll(".kd-tab").forEach((b) => b.classList.remove("kd-tab-active")); + btn.classList.add("kd-tab-active"); + currentRace = btn.dataset.race; + currentPage = 1; + await loadCards(); + }); + }); +} + +/* ── Karten laden ───────────────────────────── */ +async function loadCards() { + const grid = document.getElementById("kd-grid"); + const pagination = document.getElementById("kd-pagination"); + if (!grid) return; + + grid.innerHTML = `
Lade Karten...
`; + pagination.innerHTML = ""; + + try { + const res = await fetch(`/api/cards?race=${currentRace}&page=${currentPage}&limit=${CARDS_PER_PAGE}`); + if (!res.ok) throw new Error("API Fehler"); + const data = await res.json(); + // data = { cards: [...], total: N, page: N, totalPages: N } + + renderGrid(grid, data.cards); + renderPagination(pagination, data.totalPages, data.total); + } catch { + grid.innerHTML = `
Noch keine Karten für diese Rasse vorhanden.
`; + } +} + +/* ── Grid rendern ───────────────────────────── */ +function renderGrid(grid, cards) { + if (!cards || !cards.length) { + grid.innerHTML = `
Noch keine Karten für diese Rasse vorhanden.
`; + return; + } + + grid.innerHTML = cards.map((c) => ` +
+ ${c.name} + ${c.attack !== undefined ? `${c.attack}` : ""} + ${c.defense !== undefined ? `${c.defense}` : ""} +
${c.name}
+
+ `).join(""); +} + +/* ── Pagination rendern ─────────────────────── */ +function renderPagination(pagination, totalPages, total) { + if (totalPages <= 1) return; + + const pages = Array.from({ length: totalPages }, (_, i) => i + 1); + + pagination.innerHTML = ` + + ${pages.map((p) => ` + + `).join("")} + + ${total} Karten + `; + + document.getElementById("kd-prev")?.addEventListener("click", async () => { + if (currentPage > 1) { currentPage--; await loadCards(); } + }); + document.getElementById("kd-next")?.addEventListener("click", async () => { + if (currentPage < totalPages) { currentPage++; await loadCards(); } + }); + pagination.querySelectorAll("[data-page]").forEach((btn) => { + btn.addEventListener("click", async () => { + currentPage = parseInt(btn.dataset.page); + await loadCards(); + }); + }); +}