Merge branch 'dev'
CI/CD / deploy (push) Has been cancelled Details

This commit is contained in:
Jeff Emmett 2026-04-13 15:51:29 +00:00
commit c4bc26359c
4 changed files with 228 additions and 10 deletions

View File

@ -38,4 +38,5 @@ export const MODULE_META: Record<string, ModuleDisplayMeta> = {
rsocials: { badge: "rSo", color: "#f9a8d4", name: "rSocials", icon: "📱" }, rsocials: { badge: "rSo", color: "#f9a8d4", name: "rSocials", icon: "📱" },
rdesign: { badge: "rDe", color: "#7c3aed", name: "rDesign", icon: "🎨" }, rdesign: { badge: "rDe", color: "#7c3aed", name: "rDesign", icon: "🎨" },
rtime: { badge: "rTi", color: "#a78bfa", name: "rTime", icon: "⏳" }, rtime: { badge: "rTi", color: "#a78bfa", name: "rTime", icon: "⏳" },
rauctions: { badge: "rA", color: "#fca5a5", name: "rAuctions", icon: "🏛" },
}; };

View File

@ -0,0 +1,134 @@
/**
* <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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
#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);

View File

@ -0,0 +1,34 @@
/** Landing page body HTML for rAuctions */
export function renderLanding(): string {
return `
<div style="max-width:640px;margin:2rem auto;padding:0 1rem;text-align:center;">
<div style="font-size:3rem;margin-bottom:0.5rem;">🏛</div>
<h2 style="font-size:1.5rem;font-weight:700;margin-bottom:0.5rem;">rAuctions</h2>
<p style="color:var(--rs-text-secondary);margin-bottom:1.5rem;">
Live community auctions with real-time bidding and USDC settlement.
Create, bid on, and manage auctions for artwork, collectibles, and unique items.
</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem;text-align:left;">
<div style="padding:1rem;border-radius:8px;background:var(--rs-bg-surface);border:1px solid var(--rs-border-subtle);">
<div style="font-size:1.5rem;margin-bottom:0.25rem;">🔨</div>
<strong>Live Bidding</strong>
<p style="font-size:0.8rem;opacity:0.6;margin:0;">Real-time bids with countdown timers</p>
</div>
<div style="padding:1rem;border-radius:8px;background:var(--rs-bg-surface);border:1px solid var(--rs-border-subtle);">
<div style="font-size:1.5rem;margin-bottom:0.25rem;">💰</div>
<strong>USDC Settlement</strong>
<p style="font-size:0.8rem;opacity:0.6;margin:0;">On-chain payments via Base</p>
</div>
<div style="padding:1rem;border-radius:8px;background:var(--rs-bg-surface);border:1px solid var(--rs-border-subtle);">
<div style="font-size:1.5rem;margin-bottom:0.25rem;">📸</div>
<strong>Rich Listings</strong>
<p style="font-size:0.8rem;opacity:0.6;margin:0;">Photos, descriptions, and reserve prices</p>
</div>
<div style="padding:1rem;border-radius:8px;background:var(--rs-bg-surface);border:1px solid var(--rs-border-subtle);">
<div style="font-size:1.5rem;margin-bottom:0.25rem;">🔐</div>
<strong>EncryptID Auth</strong>
<p style="font-size:0.8rem;opacity:0.6;margin:0;">Secure identity for bidders & sellers</p>
</div>
</div>
</div>`;
}

View File

@ -1,20 +1,69 @@
/** /**
* rAuctions module Community auctions with USDC (stub). * rAuctions module community auctions with USDC bidding.
* TODO: Implement auction logic. *
* Embeds the standalone rauctions.online Next.js app via externalApp iframe.
* Provides a hub page listing active auctions proxied from the rauctions API.
*/ */
import { Hono } from 'hono'; import { Hono } from "hono";
import type { RSpaceModule } from '../../shared/module'; import { renderShell, renderExternalAppShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule } from "../../shared/module";
import { renderLanding } from "./landing";
const RAUCTIONS_URL = process.env.RAUCTIONS_URL || "https://rauctions.online";
const routes = new Hono(); const routes = new Hono();
routes.get('/', (c) => c.text('rAuctions — coming soon')); // ── Page route ──
routes.get("/", (c) => {
const spaceSlug = c.req.param("space") || "demo";
const view = c.req.query("view");
if (view === "app") {
return c.html(renderExternalAppShell({
title: `${spaceSlug} — Auctions | rSpace`,
moduleId: "rauctions",
spaceSlug,
modules: getModuleInfoList(),
appUrl: RAUCTIONS_URL,
appName: "rAuctions",
theme: "dark",
}));
}
return c.html(renderShell({
title: `${spaceSlug} — Auctions | rSpace`,
moduleId: "rauctions",
spaceSlug,
modules: getModuleInfoList(),
theme: "dark",
body: `<div class="rapp-nav" style="padding:0 1rem;margin-top:8px">
<span class="rapp-nav__title">Auctions</span>
<a href="?view=app" class="rapp-nav__btn--app-toggle">Open Full App</a>
</div>
<folk-auctions-hub space="${spaceSlug}"></folk-auctions-hub>`,
scripts: `<script type="module" src="/modules/rauctions/folk-auctions-hub.js"></script>`,
}));
});
// ── API: proxy health ──
routes.get("/api/health", (c) => {
return c.json({ status: "ok", service: "rauctions" });
});
export const auctionsModule: RSpaceModule = { export const auctionsModule: RSpaceModule = {
id: 'rauctions', id: "rauctions",
name: 'rAuctions', name: "rAuctions",
icon: '🎭', icon: "🏛",
description: 'Community auctions with USDC', description: "Live auctions with USDC bidding",
routes,
scoping: { defaultScope: 'space', userConfigurable: true }, scoping: { defaultScope: 'space', userConfigurable: true },
routes,
landingPage: renderLanding,
standaloneDomain: "rauctions.online",
externalApp: { url: RAUCTIONS_URL, name: "rAuctions" },
outputPaths: [
{ path: "live", name: "Live Auctions", icon: "🔨", description: "Currently active auctions" },
{ path: "ended", name: "Ended", icon: "🏆", description: "Completed auction results" },
],
}; };