From 18a688badecd96d4d8fb73a204e633049dd3b361 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 13 Apr 2026 15:50:25 +0000 Subject: [PATCH] feat(rauctions): add rAuctions module with hub page and external app embed Registers rauctions as an embedded rSpace module that proxies the standalone rauctions.online Next.js app. Includes hub page with active auction listings, landing page, and MODULE_META entry for canvas display. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/module-display.ts | 1 + modules/rauctions/folk-auctions-hub.ts | 134 +++++++++++++++++++++++++ modules/rauctions/landing.ts | 34 +++++++ modules/rauctions/mod.ts | 69 +++++++++++-- 4 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 modules/rauctions/folk-auctions-hub.ts create mode 100644 modules/rauctions/landing.ts diff --git a/lib/module-display.ts b/lib/module-display.ts index b9253fac..4f9e91c4 100644 --- a/lib/module-display.ts +++ b/lib/module-display.ts @@ -38,4 +38,5 @@ export const MODULE_META: Record = { rsocials: { badge: "rSo", color: "#f9a8d4", name: "rSocials", icon: "πŸ“±" }, rdesign: { badge: "rDe", color: "#7c3aed", name: "rDesign", icon: "🎨" }, rtime: { badge: "rTi", color: "#a78bfa", name: "rTime", icon: "⏳" }, + rauctions: { badge: "rA", color: "#fca5a5", name: "rAuctions", icon: "πŸ›" }, }; diff --git a/modules/rauctions/folk-auctions-hub.ts b/modules/rauctions/folk-auctions-hub.ts new file mode 100644 index 00000000..ef371bad --- /dev/null +++ b/modules/rauctions/folk-auctions-hub.ts @@ -0,0 +1,134 @@ +/** + * β€” 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 = ` +
+
πŸ›
+

No active auctions right now.

+ Create an Auction +
`; + 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 + ? `` + : `
πŸ›
`; + return ` + + ${img} +
+
${this.#esc(a.title)}
+
+ ${bid} + ${endStr ? `Ends ${endStr}` : ""} +
+
+
`; + }).join(""); + } + + #renderError(msg: string) { + const list = this.#shadow.getElementById("auction-list"); + if (!list) return; + list.innerHTML = ` +
+

⚠ ${this.#esc(msg)}

+ Open rAuctions +
`; + } + + #esc(s: string) { + return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + + #render() { + this.#shadow.innerHTML = ` + +
+

πŸ› Active Auctions

+ + New Auction +
+
+
Loading auctions…
+
`; + } +} + +customElements.define("folk-auctions-hub", FolkAuctionsHub); diff --git a/modules/rauctions/landing.ts b/modules/rauctions/landing.ts new file mode 100644 index 00000000..464d6aee --- /dev/null +++ b/modules/rauctions/landing.ts @@ -0,0 +1,34 @@ +/** Landing page body HTML for rAuctions */ +export function renderLanding(): string { + return ` +
+
πŸ›
+

rAuctions

+

+ Live community auctions with real-time bidding and USDC settlement. + Create, bid on, and manage auctions for artwork, collectibles, and unique items. +

+
+
+
πŸ”¨
+ Live Bidding +

Real-time bids with countdown timers

+
+
+
πŸ’°
+ USDC Settlement +

On-chain payments via Base

+
+
+
πŸ“Έ
+ Rich Listings +

Photos, descriptions, and reserve prices

+
+
+
πŸ”
+ EncryptID Auth +

Secure identity for bidders & sellers

+
+
+
`; +} diff --git a/modules/rauctions/mod.ts b/modules/rauctions/mod.ts index dd43246f..d689e308 100644 --- a/modules/rauctions/mod.ts +++ b/modules/rauctions/mod.ts @@ -1,20 +1,69 @@ /** - * rAuctions module β€” Community auctions with USDC (stub). - * TODO: Implement auction logic. + * rAuctions module β€” community auctions with USDC bidding. + * + * 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 type { RSpaceModule } from '../../shared/module'; +import { Hono } from "hono"; +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(); -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: `
+ Auctions + Open Full App +
+ `, + scripts: ``, + })); +}); + +// ── API: proxy health ── +routes.get("/api/health", (c) => { + return c.json({ status: "ok", service: "rauctions" }); +}); export const auctionsModule: RSpaceModule = { - id: 'rauctions', - name: 'rAuctions', - icon: '🎭', - description: 'Community auctions with USDC', - routes, + id: "rauctions", + name: "rAuctions", + icon: "πŸ›", + description: "Live auctions with USDC bidding", 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" }, + ], };