135 lines
4.6 KiB
TypeScript
135 lines
4.6 KiB
TypeScript
/**
|
|
* <folk-auctions-hub> — Displays active auctions from the rauctions.online API.
|
|
*
|
|
* Attributes:
|
|
* space — current space slug
|
|
*/
|
|
|
|
const RAUCTIONS_API = "https://rauctions.online";
|
|
|
|
class FolkAuctionsHub extends HTMLElement {
|
|
#shadow: ShadowRoot;
|
|
#space = "demo";
|
|
|
|
constructor() {
|
|
super();
|
|
this.#shadow = this.attachShadow({ mode: "open" });
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.#space = this.getAttribute("space") || "demo";
|
|
this.#render();
|
|
this.#fetchAuctions();
|
|
}
|
|
|
|
async #fetchAuctions() {
|
|
try {
|
|
const res = await fetch(`${RAUCTIONS_API}/api/auctions?status=active`, {
|
|
headers: { "Accept": "application/json" },
|
|
});
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const data = await res.json();
|
|
const auctions = Array.isArray(data) ? data : (data.auctions || []);
|
|
this.#renderAuctions(auctions);
|
|
} catch (err) {
|
|
this.#renderError(err instanceof Error ? err.message : "Failed to load auctions");
|
|
}
|
|
}
|
|
|
|
#renderAuctions(auctions: Array<{ id: string; title: string; currentBid?: number; endTime?: string; imageUrl?: string; status?: string }>) {
|
|
const list = this.#shadow.getElementById("auction-list");
|
|
if (!list) return;
|
|
|
|
if (auctions.length === 0) {
|
|
list.innerHTML = `
|
|
<div class="empty">
|
|
<div class="empty-icon">🏛</div>
|
|
<p>No active auctions right now.</p>
|
|
<a href="${RAUCTIONS_API}/auctions/create" target="_blank" rel="noopener" class="btn">Create an Auction</a>
|
|
</div>`;
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = auctions.map((a) => {
|
|
const bid = a.currentBid != null ? `$${a.currentBid.toFixed(2)}` : "No bids";
|
|
const endStr = a.endTime ? new Date(a.endTime).toLocaleString() : "";
|
|
const img = a.imageUrl
|
|
? `<img src="${a.imageUrl}" alt="" class="thumb" />`
|
|
: `<div class="thumb-placeholder">🏛</div>`;
|
|
return `
|
|
<a href="${RAUCTIONS_API}/auctions/${a.id}" target="_blank" rel="noopener" class="auction-card">
|
|
${img}
|
|
<div class="card-body">
|
|
<div class="card-title">${this.#esc(a.title)}</div>
|
|
<div class="card-meta">
|
|
<span class="bid">${bid}</span>
|
|
${endStr ? `<span class="ends">Ends ${endStr}</span>` : ""}
|
|
</div>
|
|
</div>
|
|
</a>`;
|
|
}).join("");
|
|
}
|
|
|
|
#renderError(msg: string) {
|
|
const list = this.#shadow.getElementById("auction-list");
|
|
if (!list) return;
|
|
list.innerHTML = `
|
|
<div class="empty">
|
|
<p style="color:var(--rs-error,#ef4444);">⚠ ${this.#esc(msg)}</p>
|
|
<a href="${RAUCTIONS_API}" target="_blank" rel="noopener" class="btn">Open rAuctions</a>
|
|
</div>`;
|
|
}
|
|
|
|
#esc(s: string) {
|
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
}
|
|
|
|
#render() {
|
|
this.#shadow.innerHTML = `
|
|
<style>
|
|
:host { display: block; padding: 1rem; }
|
|
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
|
|
.header h2 { font-size: 1.1rem; font-weight: 700; margin: 0; }
|
|
.btn {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
padding: 6px 14px; border-radius: 8px; border: none;
|
|
background: var(--rs-accent, #6366f1); color: #fff;
|
|
font-size: 0.8rem; font-weight: 600; cursor: pointer;
|
|
text-decoration: none; transition: opacity 0.15s;
|
|
}
|
|
.btn:hover { opacity: 0.85; }
|
|
#auction-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1rem; }
|
|
.auction-card {
|
|
display: flex; flex-direction: column;
|
|
border-radius: 10px; overflow: hidden;
|
|
border: 1px solid var(--rs-border-subtle, #333);
|
|
background: var(--rs-bg-surface, #1a1a2e);
|
|
text-decoration: none; color: inherit;
|
|
transition: border-color 0.15s, transform 0.1s;
|
|
}
|
|
.auction-card:hover { border-color: var(--rs-accent, #6366f1); transform: translateY(-1px); }
|
|
.thumb { width: 100%; height: 160px; object-fit: cover; }
|
|
.thumb-placeholder {
|
|
width: 100%; height: 160px; display: flex; align-items: center; justify-content: center;
|
|
font-size: 3rem; background: var(--rs-bg-hover, #222);
|
|
}
|
|
.card-body { padding: 0.75rem 1rem; }
|
|
.card-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 4px; }
|
|
.card-meta { display: flex; gap: 8px; font-size: 0.75rem; opacity: 0.6; }
|
|
.bid { color: var(--rs-success, #22c55e); font-weight: 600; opacity: 1; }
|
|
.empty { text-align: center; padding: 3rem 1rem; opacity: 0.6; }
|
|
.empty-icon { font-size: 3rem; margin-bottom: 0.5rem; }
|
|
.loading { text-align: center; padding: 2rem; opacity: 0.5; }
|
|
</style>
|
|
<div class="header">
|
|
<h2>🏛 Active Auctions</h2>
|
|
<a href="${RAUCTIONS_API}/auctions/create" target="_blank" rel="noopener" class="btn">+ New Auction</a>
|
|
</div>
|
|
<div id="auction-list">
|
|
<div class="loading">Loading auctions…</div>
|
|
</div>`;
|
|
}
|
|
}
|
|
|
|
customElements.define("folk-auctions-hub", FolkAuctionsHub);
|