q3z34
This commit is contained in:
parent
4b10b28588
commit
b486199ca2
@ -476,3 +476,64 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Mengenauswahl ───────────────────────── */
|
||||||
|
.baz-qty-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.baz-qty-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
background: linear-gradient(#3a2810, #1a0f04);
|
||||||
|
border: 2px solid #6b4b2a;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #f0d9a6;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: .15s;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.baz-qty-btn:hover {
|
||||||
|
border-color: #f0d060;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.baz-qty-input {
|
||||||
|
width: 52px;
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
border: 2px solid #6b4b2a;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #f0d9a6;
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 4px 0;
|
||||||
|
outline: none;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
.baz-qty-input::-webkit-inner-spin-button,
|
||||||
|
.baz-qty-input::-webkit-outer-spin-button { -webkit-appearance: none; }
|
||||||
|
.baz-qty-input:focus { border-color: #f0d060; }
|
||||||
|
.baz-qty-max {
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: linear-gradient(#4a3010, #2a1808);
|
||||||
|
border: 2px solid #c8960c;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #f0d060;
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
transition: .15s;
|
||||||
|
}
|
||||||
|
.baz-qty-max:hover {
|
||||||
|
border-color: #f0d060;
|
||||||
|
color: #fff5cc;
|
||||||
|
}
|
||||||
|
|||||||
@ -179,18 +179,37 @@ async function loadShopCards() {
|
|||||||
function showBuyConfirm(card) {
|
function showBuyConfirm(card) {
|
||||||
document.getElementById("baz-confirm-modal")?.remove();
|
document.getElementById("baz-confirm-modal")?.remove();
|
||||||
|
|
||||||
const canAfford =
|
const maxAffordable = Math.max(1, Math.min(99,
|
||||||
baz_wood >= card.price_wood &&
|
card.price_wood > 0 ? Math.floor(baz_wood / card.price_wood) : 99,
|
||||||
baz_iron >= card.price_iron &&
|
card.price_iron > 0 ? Math.floor(baz_iron / card.price_iron) : 99,
|
||||||
baz_gold >= card.price_gold &&
|
card.price_gold > 0 ? Math.floor(baz_gold / card.price_gold) : 99,
|
||||||
baz_gems >= card.price_gems;
|
card.price_gems > 0 ? Math.floor(baz_gems / card.price_gems) : 99,
|
||||||
|
));
|
||||||
|
|
||||||
const priceStr = [
|
function calcTotal(qty) {
|
||||||
card.price_wood > 0 ? `🪵 ${card.price_wood} Holz` : "",
|
return {
|
||||||
card.price_iron > 0 ? `⚙️ ${card.price_iron} Eisen` : "",
|
wood: card.price_wood * qty,
|
||||||
card.price_gold > 0 ? `🪙 ${card.price_gold} Gold` : "",
|
iron: card.price_iron * qty,
|
||||||
card.price_gems > 0 ? `💎 ${card.price_gems} Gems` : "",
|
gold: card.price_gold * qty,
|
||||||
].filter(Boolean).join(" ");
|
gems: card.price_gems * qty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function priceHtml(qty) {
|
||||||
|
const t = calcTotal(qty);
|
||||||
|
return [
|
||||||
|
t.wood > 0 ? `<span class="baz-price-wood">🪵 ${t.wood}</span>` : "",
|
||||||
|
t.iron > 0 ? `<span class="baz-price-iron">⚙️ ${t.iron}</span>` : "",
|
||||||
|
t.gold > 0 ? `<span class="baz-price-gold">🪙 ${t.gold}</span>` : "",
|
||||||
|
t.gems > 0 ? `<span class="baz-price-gems">💎 ${t.gems}</span>` : "",
|
||||||
|
].filter(Boolean).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function canAffordQty(qty) {
|
||||||
|
const t = calcTotal(qty);
|
||||||
|
return baz_wood >= t.wood && baz_iron >= t.iron &&
|
||||||
|
baz_gold >= t.gold && baz_gems >= t.gems;
|
||||||
|
}
|
||||||
|
|
||||||
const modal = document.createElement("div");
|
const modal = document.createElement("div");
|
||||||
modal.id = "baz-confirm-modal";
|
modal.id = "baz-confirm-modal";
|
||||||
@ -202,33 +221,68 @@ function showBuyConfirm(card) {
|
|||||||
onerror="this.src='/images/avatar_placeholder.svg'">
|
onerror="this.src='/images/avatar_placeholder.svg'">
|
||||||
</div>
|
</div>
|
||||||
<div class="baz-confirm-name">${card.name}</div>
|
<div class="baz-confirm-name">${card.name}</div>
|
||||||
<div class="baz-confirm-price">${priceStr || "Kostenlos"}</div>
|
<div class="baz-qty-wrap">
|
||||||
${!canAfford ? `<div class="baz-confirm-warn">⚠ Nicht genug Ressourcen</div>` : ""}
|
<button class="baz-qty-btn" id="baz-qty-minus">−</button>
|
||||||
|
<input class="baz-qty-input" id="baz-qty" type="number"
|
||||||
|
min="1" max="99" value="1">
|
||||||
|
<button class="baz-qty-btn" id="baz-qty-plus">+</button>
|
||||||
|
<button class="baz-qty-max" id="baz-qty-max">MAX</button>
|
||||||
|
</div>
|
||||||
|
<div class="baz-confirm-price" id="baz-total-price">${priceHtml(1) || "Kostenlos"}</div>
|
||||||
|
<div class="baz-confirm-warn" id="baz-afford-warn" style="display:none">⚠ Nicht genug Ressourcen</div>
|
||||||
<div class="baz-confirm-btns">
|
<div class="baz-confirm-btns">
|
||||||
<button class="baz-btn-cancel" id="baz-cancel">Abbrechen</button>
|
<button class="baz-btn-cancel" id="baz-cancel">Abbrechen</button>
|
||||||
<button class="baz-btn-buy" id="baz-confirm" ${!canAfford?"disabled":""}>Kaufen</button>
|
<button class="baz-btn-buy" id="baz-confirm">Kaufen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
document.getElementById("bazaar-popup").appendChild(modal);
|
document.getElementById("bazaar-popup").appendChild(modal);
|
||||||
|
|
||||||
|
const qtyInput = modal.querySelector("#baz-qty");
|
||||||
|
const priceEl = modal.querySelector("#baz-total-price");
|
||||||
|
const warnEl = modal.querySelector("#baz-afford-warn");
|
||||||
|
const buyBtn = modal.querySelector("#baz-confirm");
|
||||||
|
|
||||||
|
function updateModal() {
|
||||||
|
const qty = Math.max(1, Math.min(99, parseInt(qtyInput.value) || 1));
|
||||||
|
qtyInput.value = qty;
|
||||||
|
priceEl.innerHTML = priceHtml(qty) || "Kostenlos";
|
||||||
|
const ok = canAffordQty(qty);
|
||||||
|
warnEl.style.display = ok ? "none" : "";
|
||||||
|
buyBtn.disabled = !ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
qtyInput.addEventListener("input", updateModal);
|
||||||
|
modal.querySelector("#baz-qty-minus").onclick = () => {
|
||||||
|
qtyInput.value = Math.max(1, parseInt(qtyInput.value || 1) - 1); updateModal();
|
||||||
|
};
|
||||||
|
modal.querySelector("#baz-qty-plus").onclick = () => {
|
||||||
|
qtyInput.value = Math.min(99, parseInt(qtyInput.value || 1) + 1); updateModal();
|
||||||
|
};
|
||||||
|
modal.querySelector("#baz-qty-max").onclick = () => {
|
||||||
|
qtyInput.value = maxAffordable; updateModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
updateModal();
|
||||||
|
|
||||||
modal.querySelector("#baz-cancel").onclick = () => modal.remove();
|
modal.querySelector("#baz-cancel").onclick = () => modal.remove();
|
||||||
modal.querySelector(".baz-confirm-backdrop").onclick = () => modal.remove();
|
modal.querySelector(".baz-confirm-backdrop").onclick = () => modal.remove();
|
||||||
modal.querySelector("#baz-confirm").onclick = async () => {
|
|
||||||
const btn = modal.querySelector("#baz-confirm");
|
buyBtn.onclick = async () => {
|
||||||
btn.disabled = true; btn.textContent = "…";
|
const qty = Math.max(1, Math.min(99, parseInt(qtyInput.value) || 1));
|
||||||
|
buyBtn.disabled = true; buyBtn.textContent = "…";
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/bazaar/buy", {
|
const res = await fetch("/api/bazaar/buy", {
|
||||||
method:"POST", headers:{"Content-Type":"application/json"},
|
method:"POST", headers:{"Content-Type":"application/json"},
|
||||||
body: JSON.stringify({ card_id: card.id }),
|
body: JSON.stringify({ card_id: card.id, quantity: qty }),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) { btn.textContent = data.error||"Fehler"; setTimeout(()=>modal.remove(),2000); return; }
|
if (!res.ok) { buyBtn.textContent = data.error||"Fehler"; setTimeout(()=>modal.remove(),2000); return; }
|
||||||
baz_wood=data.wood; baz_iron=data.iron; baz_gold=data.gold; baz_gems=data.gems;
|
baz_wood=data.wood; baz_iron=data.iron; baz_gold=data.gold; baz_gems=data.gems;
|
||||||
updateCurrencyDisplay();
|
updateCurrencyDisplay();
|
||||||
modal.remove();
|
modal.remove();
|
||||||
showToast(`✅ ${card.name} gekauft!`);
|
showToast(`✅ ${qty}× ${card.name} gekauft!`);
|
||||||
await loadShopCards();
|
await loadShopCards();
|
||||||
} catch { btn.textContent="Fehler"; setTimeout(()=>modal.remove(),2000); }
|
} catch { buyBtn.textContent="Fehler"; setTimeout(()=>modal.remove(),2000); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,8 +76,9 @@ router.get("/bazaar/cards", requireLogin, async (req, res) => {
|
|||||||
════════════════════════════════════════════ */
|
════════════════════════════════════════════ */
|
||||||
router.post("/bazaar/buy", requireLogin, async (req, res) => {
|
router.post("/bazaar/buy", requireLogin, async (req, res) => {
|
||||||
const userId = req.session.user.id;
|
const userId = req.session.user.id;
|
||||||
const { card_id } = req.body;
|
const { card_id, quantity } = req.body;
|
||||||
if (!card_id) return res.status(400).json({ error: "card_id fehlt." });
|
if (!card_id) return res.status(400).json({ error: "card_id fehlt." });
|
||||||
|
const qty = Math.max(1, Math.min(99, parseInt(quantity) || 1));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [[card]] = await db.query(
|
const [[card]] = await db.query(
|
||||||
@ -117,10 +118,10 @@ router.post("/bazaar/buy", requireLogin, async (req, res) => {
|
|||||||
{ key: "gems", label: "Gems" },
|
{ key: "gems", label: "Gems" },
|
||||||
];
|
];
|
||||||
for (const { key, label } of checks) {
|
for (const { key, label } of checks) {
|
||||||
const need = card[`price_${key}`];
|
const need = card[`price_${key}`] * qty;
|
||||||
if (need > 0 && (currency[key] || 0) < need) {
|
if (need > 0 && (currency[key] || 0) < need) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: `Nicht genug ${label}. Benötigt: ${need}, Vorhanden: ${currency[key] || 0}`,
|
error: `Nicht genug ${label}. Benötigt: ${need} (${qty}×), Vorhanden: ${currency[key] || 0}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,18 +134,18 @@ router.post("/bazaar/buy", requireLogin, async (req, res) => {
|
|||||||
gems = gems - ?
|
gems = gems - ?
|
||||||
WHERE account_id = ?`,
|
WHERE account_id = ?`,
|
||||||
[
|
[
|
||||||
card.price_wood,
|
card.price_wood * qty,
|
||||||
card.price_iron,
|
card.price_iron * qty,
|
||||||
card.price_gold,
|
card.price_gold * qty,
|
||||||
card.price_gems,
|
card.price_gems * qty,
|
||||||
userId,
|
userId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
await db.query(
|
await db.query(
|
||||||
`INSERT INTO user_cards (user_id, card_id, amount) VALUES (?, ?, 1)
|
`INSERT INTO user_cards (user_id, card_id, amount) VALUES (?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE amount = amount + 1`,
|
ON DUPLICATE KEY UPDATE amount = amount + ?`,
|
||||||
[userId, card_id],
|
[userId, card_id, qty, qty],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [[updated]] = await db.query(
|
const [[updated]] = await db.query(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user