dukt
This commit is contained in:
parent
1af0496722
commit
0fdfda8b0a
79
app.js
79
app.js
@ -443,15 +443,90 @@ app.get("/api/energy", requireLogin, async (req, res) => {
|
||||
const ENERGY_MAX = 40;
|
||||
try {
|
||||
const [[row]] = await db.query(
|
||||
"SELECT energy FROM account_currency WHERE account_id = ?",
|
||||
"SELECT energy, energy_bought FROM account_currency WHERE account_id = ?",
|
||||
[userId],
|
||||
);
|
||||
res.json({ energy: row?.energy ?? ENERGY_MAX, energy_max: ENERGY_MAX });
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const boughtDate = row?.energy_bought
|
||||
? new Date(row.energy_bought).toISOString().slice(0, 10)
|
||||
: null;
|
||||
res.json({
|
||||
energy: row?.energy ?? ENERGY_MAX,
|
||||
energy_max: ENERGY_MAX,
|
||||
bought_today: boughtDate === today,
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: "DB Fehler" });
|
||||
}
|
||||
});
|
||||
|
||||
/* ========================
|
||||
Energie kaufen
|
||||
======================== */
|
||||
|
||||
app.post("/api/energy/buy", requireLogin, async (req, res) => {
|
||||
const userId = req.session.user.id;
|
||||
const ENERGY_MAX = 40;
|
||||
const ENERGY_BUY = 10; // Energie die dazugekauft wird
|
||||
const COST_GEMS = 10;
|
||||
const COST_GOLD = 200;
|
||||
const { currency: payWith } = req.body; // "gems" oder "gold"
|
||||
|
||||
if (!["gems", "gold"].includes(payWith)) {
|
||||
return res.status(400).json({ error: "Ungültige Zahlungsmethode." });
|
||||
}
|
||||
|
||||
try {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const [[row]] = await db.query(
|
||||
"SELECT energy, gems, gold, energy_bought FROM account_currency WHERE account_id = ?",
|
||||
[userId],
|
||||
);
|
||||
if (!row) return res.status(404).json({ error: "Account nicht gefunden." });
|
||||
|
||||
/* Bereits heute gekauft? */
|
||||
const boughtDate = row.energy_bought
|
||||
? new Date(row.energy_bought).toISOString().slice(0, 10)
|
||||
: null;
|
||||
if (boughtDate === today) {
|
||||
return res.status(400).json({ error: "Du hast heute bereits Energie aufgefüllt." });
|
||||
}
|
||||
|
||||
/* Genug Währung? */
|
||||
if (payWith === "gems" && row.gems < COST_GEMS) {
|
||||
return res.status(400).json({ error: `Nicht genug Gems. Benötigt: ${COST_GEMS}.` });
|
||||
}
|
||||
if (payWith === "gold" && row.gold < COST_GOLD) {
|
||||
return res.status(400).json({ error: `Nicht genug Gold. Benötigt: ${COST_GOLD}.` });
|
||||
}
|
||||
|
||||
/* Energie darf max auf ENERGY_MAX steigen */
|
||||
const newEnergy = Math.min((row.energy ?? ENERGY_MAX) + ENERGY_BUY, ENERGY_MAX);
|
||||
|
||||
if (payWith === "gems") {
|
||||
await db.query(
|
||||
`UPDATE account_currency
|
||||
SET energy = ?, gems = gems - ?, energy_bought = ?
|
||||
WHERE account_id = ?`,
|
||||
[newEnergy, COST_GEMS, today, userId],
|
||||
);
|
||||
} else {
|
||||
await db.query(
|
||||
`UPDATE account_currency
|
||||
SET energy = ?, gold = gold - ?, energy_bought = ?
|
||||
WHERE account_id = ?`,
|
||||
[newEnergy, COST_GOLD, today, userId],
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ success: true, energy: newEnergy, energy_max: ENERGY_MAX });
|
||||
} catch (err) {
|
||||
console.error("[Energy Buy]", err);
|
||||
res.status(500).json({ error: "DB Fehler" });
|
||||
}
|
||||
});
|
||||
|
||||
/* ========================
|
||||
404 Handler
|
||||
======================== */
|
||||
|
||||
198
public/js/hud.js
198
public/js/hud.js
@ -67,3 +67,201 @@ function formatNumber(n) {
|
||||
if (n === undefined || n === null) return "0";
|
||||
return Number(n).toLocaleString("de-DE");
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════
|
||||
Energie kaufen – Popup beim + Klick
|
||||
════════════════════════════════════════════ */
|
||||
|
||||
document.getElementById("hud-energy-plus")?.addEventListener("click", openEnergyBuyPopup);
|
||||
|
||||
async function openEnergyBuyPopup() {
|
||||
/* Aktuellen Stand laden */
|
||||
let hud, energyStatus;
|
||||
try {
|
||||
[hud, energyStatus] = await Promise.all([
|
||||
fetch("/api/hud").then(r => r.json()),
|
||||
fetch("/api/energy").then(r => r.json()),
|
||||
]);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Altes Popup entfernen */
|
||||
document.getElementById("energy-buy-overlay")?.remove();
|
||||
|
||||
const alreadyBought = energyStatus.bought_today;
|
||||
const energyFull = (energyStatus.energy ?? 0) >= (energyStatus.energy_max ?? 40);
|
||||
const canGems = hud.gems >= 10;
|
||||
const canGold = hud.gold >= 200;
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.id = "energy-buy-overlay";
|
||||
overlay.style.cssText = `
|
||||
position:fixed; inset:0; z-index:99999;
|
||||
background:rgba(0,0,0,.75);
|
||||
display:flex; align-items:center; justify-content:center;`;
|
||||
|
||||
/* ── Inhalt je nach Status ── */
|
||||
let body = "";
|
||||
|
||||
if (alreadyBought) {
|
||||
body = `
|
||||
<div style="font-size:36px;margin-bottom:12px;">⏳</div>
|
||||
<div style="font-family:'Cinzel',serif;font-size:16px;color:#f0d060;
|
||||
letter-spacing:2px;margin-bottom:10px;">BEREITS AUFGEFÜLLT</div>
|
||||
<p style="font-family:'Cinzel',serif;font-size:12px;color:#a08060;
|
||||
line-height:1.8;margin:0 0 20px;">
|
||||
Du hast heute bereits Energie dazugekauft.<br>
|
||||
<strong style="color:#f0d060;">Morgen wieder verfügbar.</strong>
|
||||
</p>`;
|
||||
} else if (energyFull) {
|
||||
body = `
|
||||
<div style="font-size:36px;margin-bottom:12px;">⚡</div>
|
||||
<div style="font-family:'Cinzel',serif;font-size:16px;color:#7de87d;
|
||||
letter-spacing:2px;margin-bottom:10px;">ENERGIE VOLL</div>
|
||||
<p style="font-family:'Cinzel',serif;font-size:12px;color:#a08060;
|
||||
line-height:1.8;margin:0 0 20px;">
|
||||
Deine Energie ist bereits auf dem Maximum.
|
||||
</p>`;
|
||||
} else {
|
||||
const gemBtn = canGems
|
||||
? `<button class="ebtn" data-pay="gems"
|
||||
style="background:linear-gradient(#1a2a4a,#0a1a3a);border:2px solid #4a7acc;
|
||||
border-radius:9px;color:#a0c8ff;font-family:'Cinzel',serif;
|
||||
font-size:12px;padding:12px 20px;cursor:pointer;flex:1;transition:.15s;">
|
||||
<div style="font-size:22px;margin-bottom:5px;">💠</div>
|
||||
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">10 Gems</div>
|
||||
<div style="font-size:10px;color:#6090c0;">Verfügbar: ${formatNum(hud.gems)}</div>
|
||||
</button>`
|
||||
: `<button disabled style="background:rgba(20,20,30,.6);border:2px solid rgba(80,100,140,.3);
|
||||
border-radius:9px;color:rgba(100,130,180,.4);font-family:'Cinzel',serif;
|
||||
font-size:12px;padding:12px 20px;flex:1;cursor:not-allowed;">
|
||||
<div style="font-size:22px;margin-bottom:5px;">💠</div>
|
||||
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">10 Gems</div>
|
||||
<div style="font-size:10px;">Zu wenig Gems</div>
|
||||
</button>`;
|
||||
|
||||
const goldBtn = canGold
|
||||
? `<button class="ebtn" data-pay="gold"
|
||||
style="background:linear-gradient(#2a1a04,#1a0f02);border:2px solid #c8960c;
|
||||
border-radius:9px;color:#f0d060;font-family:'Cinzel',serif;
|
||||
font-size:12px;padding:12px 20px;cursor:pointer;flex:1;transition:.15s;">
|
||||
<div style="font-size:22px;margin-bottom:5px;">🪙</div>
|
||||
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">200 Gold</div>
|
||||
<div style="font-size:10px;color:#a08040;">Verfügbar: ${formatNum(hud.gold)}</div>
|
||||
</button>`
|
||||
: `<button disabled style="background:rgba(20,15,5,.6);border:2px solid rgba(140,100,20,.3);
|
||||
border-radius:9px;color:rgba(180,140,40,.4);font-family:'Cinzel',serif;
|
||||
font-size:12px;padding:12px 20px;flex:1;cursor:not-allowed;">
|
||||
<div style="font-size:22px;margin-bottom:5px;">🪙</div>
|
||||
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">200 Gold</div>
|
||||
<div style="font-size:10px;">Zu wenig Gold</div>
|
||||
</button>`;
|
||||
|
||||
body = `
|
||||
<div style="font-size:36px;margin-bottom:10px;">⚡</div>
|
||||
<div style="font-family:'Cinzel',serif;font-size:16px;color:#f0d060;
|
||||
letter-spacing:2px;margin-bottom:6px;">ENERGIE AUFFÜLLEN</div>
|
||||
<p style="font-family:'Cinzel',serif;font-size:11px;color:#a08060;
|
||||
margin:0 0 18px;line-height:1.7;">
|
||||
+10 Energie · max. 1× täglich
|
||||
</p>
|
||||
<div style="display:flex;gap:12px;width:100%;">${gemBtn}${goldBtn}</div>
|
||||
${!canGems && !canGold
|
||||
? `<p style="font-family:'Cinzel',serif;font-size:11px;color:#e74c3c;
|
||||
margin:12px 0 0;">Weder genug Gems noch Gold vorhanden.</p>`
|
||||
: ""}`;
|
||||
}
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div style="
|
||||
background:linear-gradient(135deg,#1a0f04,#0a0803);
|
||||
border:2px solid #c8960c; border-radius:14px;
|
||||
padding:28px 32px; max-width:360px; width:90%;
|
||||
text-align:center;
|
||||
box-shadow:0 20px 60px rgba(0,0,0,.9);
|
||||
font-family:'Cinzel',serif;">
|
||||
${body}
|
||||
<button id="ebtn-close"
|
||||
style="margin-top:16px;background:none;border:1px solid rgba(200,150,60,.35);
|
||||
border-radius:7px;color:#806040;font-family:'Cinzel',serif;
|
||||
font-size:11px;padding:7px 22px;cursor:pointer;transition:.15s;">
|
||||
Schließen
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
/* Schließen */
|
||||
document.getElementById("ebtn-close").addEventListener("click", () => overlay.remove());
|
||||
overlay.addEventListener("click", e => { if (e.target === overlay) overlay.remove(); });
|
||||
|
||||
/* Kauf-Buttons */
|
||||
overlay.querySelectorAll(".ebtn").forEach(btn => {
|
||||
btn.addEventListener("mouseenter", () => { btn.style.transform = "translateY(-2px)"; btn.style.opacity = ".9"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.transform = ""; btn.style.opacity = ""; });
|
||||
btn.addEventListener("click", () => buyEnergy(btn.dataset.pay, overlay));
|
||||
});
|
||||
}
|
||||
|
||||
async function buyEnergy(payWith, overlay) {
|
||||
/* Buttons deaktivieren während Kauf */
|
||||
overlay.querySelectorAll(".ebtn").forEach(b => { b.disabled = true; b.style.opacity = ".5"; });
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/energy/buy", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ currency: payWith }),
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
overlay.querySelector("div > div:last-of-type").insertAdjacentHTML(
|
||||
"beforebegin",
|
||||
`<p style="font-family:'Cinzel',serif;font-size:11px;color:#e74c3c;
|
||||
margin:8px 0 0;">${data.error || "Fehler beim Kauf."}</p>`
|
||||
);
|
||||
overlay.querySelectorAll(".ebtn").forEach(b => { b.disabled = false; b.style.opacity = ""; });
|
||||
return;
|
||||
}
|
||||
|
||||
/* Erfolg: HUD aktualisieren + Popup mit Bestätigung */
|
||||
await refreshHud();
|
||||
overlay.remove();
|
||||
|
||||
const confirm = document.createElement("div");
|
||||
confirm.style.cssText = `
|
||||
position:fixed; inset:0; z-index:99999;
|
||||
background:rgba(0,0,0,.75);
|
||||
display:flex; align-items:center; justify-content:center;`;
|
||||
confirm.innerHTML = `
|
||||
<div style="background:linear-gradient(135deg,#0a1a08,#041004);
|
||||
border:2px solid #4a8a3c; border-radius:14px;
|
||||
padding:28px 36px; text-align:center; max-width:300px;
|
||||
box-shadow:0 20px 60px rgba(0,0,0,.9); font-family:'Cinzel',serif;">
|
||||
<div style="font-size:44px;margin-bottom:10px;">⚡</div>
|
||||
<div style="font-size:16px;color:#7de87d;letter-spacing:2px;margin-bottom:8px;">
|
||||
+10 ENERGIE
|
||||
</div>
|
||||
<p style="font-size:12px;color:#a0c890;margin:0 0 18px;">
|
||||
Energie aufgefüllt auf ${data.energy} / ${data.energy_max}
|
||||
</p>
|
||||
<button onclick="this.closest('div[style]').remove()"
|
||||
style="background:linear-gradient(#1a4a18,#0f2a0e);border:2px solid #4a8a3c;
|
||||
border-radius:7px;color:#a0e090;font-family:'Cinzel',serif;
|
||||
font-size:12px;padding:8px 24px;cursor:pointer;">
|
||||
✔ OK
|
||||
</button>
|
||||
</div>`;
|
||||
document.body.appendChild(confirm);
|
||||
setTimeout(() => confirm.remove(), 3000);
|
||||
|
||||
} catch {
|
||||
overlay.querySelectorAll(".ebtn").forEach(b => { b.disabled = false; b.style.opacity = ""; });
|
||||
}
|
||||
}
|
||||
|
||||
function formatNum(n) {
|
||||
return Number(n || 0).toLocaleString("de-DE");
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user