Add files via upload

This commit is contained in:
James 2025-10-04 19:21:45 -07:00 committed by GitHub
parent 4f16ac7a44
commit a534b65833
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 64 additions and 38 deletions

View File

@ -338,7 +338,7 @@
<div class="wrap">
<div class="row">
<h1>BESTWCOAST Shop</h1>
<a class="cart-btn" href="/auth/discord" id="loginDiscord">Login with Discord</a>
<a class="cart-btn" href="https://discord.gg/kQrAQSSrez" id="loginDiscord">Login to our Discord</a>
<span id="whoami" class="muted"></span>
<div class="spacer"></div>
<nav class="links" aria-label="Primary">
@ -463,7 +463,8 @@
<script>
const API = "https://affiliated-lets-automatic-oak.trycloudflare.com";
const CAPACITY_IDS = new Set(['server1', 'server2', 'server3']);
let remaining = 12;
/* ===== Login banner (whoami) ===== */
async function getWhoAmI() {
@ -510,36 +511,41 @@
function cartTotal() { return Object.entries(state.cart).reduce((s, [id, q]) => { const p = products.find(p => p.id === id); return s + (p ? p.price * q : 0) }, 0); }
const isServer = id => id === 'server1' || id === 'server2' || id === 'server3';
function serversInCart() {
return Object.entries(state.cart)
.filter(([id]) => isServer(id))
.reduce((a, [, q]) => a + q, 0);
return Object.entries(state.cart || {})
.reduce((n, [id, qty]) => n + (CAPACITY_IDS.has(id) ? Number(qty || 0) : 0), 0);
}
function canAddServers(qtyToAdd) {
return serversInCart() + qtyToAdd <= remaining;
}
/* ===== Render ===== */
function cardHtml(p) {
const inCart = state.cart[p.id] || 0;
let infoBtn = '';
if (p.name === 'Server Class 1') infoBtn = `<button class="btn btn-secondary" data-info="server-class-1">Info</button> <span class="chip" data-remaining>Remaining: <span class="remN">?</span>/12</span>`;
if (p.name === 'Server Class 2') infoBtn = `<button class="btn btn-secondary" data-info="server-class-2">Info</button> <span class="chip" data-remaining>Remaining: <span class="remN">?</span>/12</span>`;
if (p.name === 'Server Class 3') infoBtn = `<button class="btn btn-secondary" data-info="server-class-3">Info</button> <span class="chip" data-remaining>Remaining: <span class="remN">?</span>/12</span>`;
function cardHtml(p) {
const remHtml = CAPACITY_IDS.has(p.id)
? `<div class="rem-row">Remaining: <span class="remN" data-rem-for="${p.id}">12</span>/12</div>`
: `<div class="rem-row text-muted">Unlimited</div>`;
const inCart = state.cart[p.id] || 0;
let infoBtn = '';
if (p.name === 'Server Class 1') infoBtn = `<button class="btn btn-secondary" data-info="server-class-1">Info</button>`;
if (p.name === 'Server Class 2') infoBtn = `<button class="btn btn-secondary" data-info="server-class-2">Info</button>`;
if (p.name === 'Server Class 3') infoBtn = `<button class="btn btn-secondary" data-info="server-class-3">Info</button>`;
return `<article class="card" data-product="${p.id}">
<div class="img">${p.img ? `<img src="${p.img}" alt="${p.name}">` : `<canvas data-id="${p.id}" width="320" height="150"></canvas>`}</div>
<div class="footer">
<h3>${p.name}</h3>
<div class="meta">${p.tag || ''}</div>
<div class="price">${fmt(p.price)}</div>
<div class="chip">${p.category}</div>
${remHtml}
<div class="actions">
<button class="btn" data-add="${p.id}">${inCart ? 'Add another' : 'Add to cart'}</button>
${infoBtn}
</div>
</div>
</article>`;
}
return `<article class="card">
<div class="img">${p.img ? `<img src="${p.img}" alt="${p.name}">` : `<canvas data-id="${p.id}" width="320" height="150"></canvas>`}</div>
<div class="footer">
<h3>${p.name}</h3>
<div class="meta">${p.tag || ''}</div>
<div class="price">${fmt(p.price)}</div>
<div class="chip">${p.category}</div>
<div class="actions">
<button class="btn" data-add="${p.id}">${inCart ? 'Add another' : 'Add to cart'}</button>
${infoBtn}
</div>
</div>
</article>`;
}
@ -560,6 +566,7 @@
case 'name': list.sort((a, b) => a.name.localeCompare(b.name)); break;
}
grid.innerHTML = list.map(cardHtml).join('');
refreshRemaining();
updateCartUi();
}
@ -581,6 +588,8 @@
$('#cartTotal').textContent = fmt(cartTotal());
const rows = Object.entries(state.cart);
$('#cartItems').innerHTML = rows.length ? rows.map(([id, qty]) => rowHtml(id, qty)).join('') : '<div class="muted">Cart is empty.</div>';
}
/* ===== Events ===== */
@ -601,7 +610,8 @@
return;
}
state.cart[id] = (state.cart[id] || 0) + 1;
saveCart(); render();
saveCart(); render(); refreshRemaining();
}
if (inc) {
@ -612,14 +622,16 @@
return;
}
state.cart[id] = (state.cart[id] || 0) + 1;
saveCart(); updateCartUi();
saveCart(); updateCartUi(); refreshRemaining();
}
if (dec) {
const id = dec.getAttribute('data-dec');
state.cart[id] = Math.max(0, (state.cart[id] || 0) - 1);
if (state.cart[id] === 0) delete state.cart[id];
saveCart(); render();
saveCart(); render(); refreshRemaining();
}
});
@ -729,7 +741,7 @@
}
let remaining = 12;
async function refreshRemaining() {
try {
@ -738,18 +750,32 @@
const j = await r.json();
if (typeof j.remaining === 'number') remaining = j.remaining;
// update badges
document.querySelectorAll('[data-remaining] .remN')
.forEach(s => s.textContent = remaining);
// For each card, set remaining or ∞ and enable/disable add button
document.querySelectorAll('[data-rem-for]').forEach(el => {
const id = el.dataset.remFor;
const card = el.closest('[data-product]');
const addBtn = card?.querySelector(`[data-add="${id}"]`);
// disable server buttons if sold out relative to what's already in cart
const left = Math.max(0, remaining - serversInCart());
const disable = left <= 0;
document.querySelectorAll('[data-add="server1"],[data-add="server2"],[data-add="server3"]')
.forEach(btn => { btn.disabled = disable; btn.textContent = disable ? 'Sold out' : 'Add to cart'; });
} catch { }
if (CAPACITY_IDS.has(id)) {
const left = Math.max(0, remaining - serversInCart());
el.textContent = left; // just the current number
if (addBtn) {
addBtn.disabled = left <= 0;
addBtn.textContent = left <= 0 ? 'Sold out' : 'Add to cart';
}
} else {
el.textContent = '∞';
if (addBtn) {
addBtn.disabled = false;
addBtn.textContent = 'Add to cart';
}
}
});
} catch { /* ignore */ }
}
</script>