/** * — browse catalog, view orders, trigger fulfillment. * Shows catalog items, order creation flow, and order status tracking. */ class FolkCartShop extends HTMLElement { private shadow: ShadowRoot; private space = "default"; private catalog: any[] = []; private orders: any[] = []; private view: "catalog" | "orders" = "catalog"; private loading = true; constructor() { super(); this.shadow = this.attachShadow({ mode: "open" }); } connectedCallback() { // Resolve space from attribute or URL path const attr = this.getAttribute("space"); if (attr) { this.space = attr; } else { const parts = window.location.pathname.split("/").filter(Boolean); this.space = parts.length >= 1 ? parts[0] : "default"; } if (this.space === "demo") { this.loadDemoData(); return; } this.render(); this.loadData(); } private loadDemoData() { const now = Date.now(); this.catalog = [ { id: "demo-cat-1", title: "The Commons", description: "A pocket book exploring shared resources and collective stewardship.", price: 12, currency: "USD", tags: ["books"], product_type: "pocket book", status: "active", created_at: new Date(now - 30 * 86400000).toISOString(), }, { id: "demo-cat-2", title: "Mycelium Networks", description: "Illustrated poster mapping underground fungal communication pathways.", price: 18, currency: "USD", tags: ["prints"], product_type: "poster", status: "active", created_at: new Date(now - 25 * 86400000).toISOString(), }, { id: "demo-cat-3", title: "#DefectFi", description: "Organic cotton tee shirt with the #DefectFi campaign logo.", price: 25, currency: "USD", tags: ["apparel"], product_type: "tee shirt", status: "active", created_at: new Date(now - 20 * 86400000).toISOString(), }, { id: "demo-cat-4", title: "Cosmolocal Sticker Sheet", description: "Die-cut sticker sheet with cosmolocal design motifs.", price: 5, currency: "USD", tags: ["stickers"], product_type: "sticker sheet", status: "active", created_at: new Date(now - 15 * 86400000).toISOString(), }, { id: "demo-cat-5", title: "Doughnut Economics", description: "A zine breaking down Kate Raworth's doughnut economics framework.", price: 8, currency: "USD", tags: ["books"], product_type: "zine", status: "active", created_at: new Date(now - 10 * 86400000).toISOString(), }, { id: "demo-cat-6", title: "rSpace Logo", description: "Embroidered patch featuring the rSpace logo on twill backing.", price: 6, currency: "USD", tags: ["accessories"], product_type: "embroidered patch", status: "active", created_at: new Date(now - 5 * 86400000).toISOString(), }, { id: "demo-cat-7", title: "Cosmolocal Network Tee", description: "Bella+Canvas 3001 tee with the Cosmolocal Network radial design. DTG printed by local providers or Printful.", price: 25, currency: "USD", tags: ["apparel", "cosmolocal"], product_type: "tee", required_capabilities: ["dtg-print"], status: "active", created_at: new Date(now - 3 * 86400000).toISOString(), }, { id: "demo-cat-8", title: "Cosmolocal Sticker Sheet", description: "Kiss-cut vinyl sticker sheet with cosmolocal network motifs. Weatherproof and UV-resistant.", price: 5, currency: "USD", tags: ["stickers", "cosmolocal"], product_type: "sticker-sheet", required_capabilities: ["vinyl-cut"], status: "active", created_at: new Date(now - 1 * 86400000).toISOString(), }, ]; this.orders = [ { id: "demo-ord-1001", items: [ { title: "The Commons", qty: 1, price: 12 }, { title: "Mycelium Networks", qty: 1, price: 18 }, ], total: 30, total_price: "30.00", currency: "USD", status: "paid", created_at: new Date(now - 2 * 86400000).toISOString(), customer_email: "reader@example.com", artifact_title: "Order #1001", quantity: 2, }, { id: "demo-ord-1002", items: [ { title: "#DefectFi", qty: 1, price: 25 }, ], total: 25, total_price: "25.00", currency: "USD", status: "pending", created_at: new Date(now - 1 * 86400000).toISOString(), customer_email: "activist@example.com", artifact_title: "Order #1002", quantity: 1, }, { id: "demo-ord-1003", items: [ { title: "Cosmolocal Sticker Sheet", qty: 1, price: 5 }, { title: "Doughnut Economics", qty: 1, price: 8 }, { title: "rSpace Logo", qty: 1, price: 6 }, ], total: 23, total_price: "23.00", currency: "USD", status: "shipped", created_at: new Date(now - 5 * 86400000).toISOString(), customer_email: "maker@example.com", artifact_title: "Order #1003", quantity: 3, }, ]; this.loading = false; this.render(); } private getApiBase(): string { const path = window.location.pathname; const match = path.match(/^(\/[^/]+)?\/rcart/); return match ? match[0] : "/rcart"; } private async loadData() { this.loading = true; this.render(); try { const [catRes, ordRes] = await Promise.all([ fetch(`${this.getApiBase()}/api/catalog?limit=50`), fetch(`${this.getApiBase()}/api/orders?limit=20`), ]); const catData = await catRes.json(); const ordData = await ordRes.json(); this.catalog = catData.entries || []; this.orders = ordData.orders || []; } catch (e) { console.error("Failed to load cart data:", e); } this.loading = false; this.render(); } private render() { this.shadow.innerHTML = `
Shop
${this.loading ? `
⏳ Loading...
` : this.view === "catalog" ? this.renderCatalog() : this.renderOrders()} `; this.shadow.querySelectorAll(".tab").forEach((el) => { el.addEventListener("click", () => { this.view = ((el as HTMLElement).dataset.view || "catalog") as "catalog" | "orders"; this.render(); }); }); } private renderCatalog(): string { if (this.catalog.length === 0) { return `
No items in the catalog yet. Ingest artifacts from rPubs or Swag Designer to list them here.
`; } return `
${this.catalog.map((entry) => `

${this.esc(entry.title || "Untitled")}

${entry.product_type ? `${this.esc(entry.product_type)}` : ""} ${(entry.required_capabilities || []).map((cap: string) => `${this.esc(cap)}`).join("")} ${(entry.tags || []).map((t: string) => `${this.esc(t)}`).join("")}
${entry.description ? `
${this.esc(entry.description)}
` : ""} ${entry.dimensions ? `
${entry.dimensions.width_mm}x${entry.dimensions.height_mm}mm
` : ""} ${entry.price != null ? `
$${parseFloat(entry.price).toFixed(2)} ${entry.currency || ""}
` : ""}
${entry.status}
`).join("")}
`; } private renderOrders(): string { if (this.orders.length === 0) { return `
No orders yet.
`; } return `
${this.orders.map((order) => `

${this.esc(order.artifact_title || "Order")}

${order.provider_name ? `Provider: ${this.esc(order.provider_name)}` : ""} ${order.quantity > 1 ? ` • Qty: ${order.quantity}` : ""}
${order.status}
$${parseFloat(order.total_price || 0).toFixed(2)}
`).join("")}
`; } private esc(s: string): string { const d = document.createElement("div"); d.textContent = s; return d.innerHTML; } } customElements.define("folk-cart-shop", FolkCartShop);