diff --git a/modules/rcart/components/folk-cart-shop.ts b/modules/rcart/components/folk-cart-shop.ts index df7fd9a..fbe9c7e 100644 --- a/modules/rcart/components/folk-cart-shop.ts +++ b/modules/rcart/components/folk-cart-shop.ts @@ -21,10 +21,11 @@ class FolkCartShop extends HTMLElement { private carts: any[] = []; private payments: any[] = []; private groupBuys: any[] = []; - private view: "carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "payments" | "group-buys" = "carts"; + private view: "carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "order-detail" | "payments" | "group-buys" = "carts"; private selectedCartId: string | null = null; private selectedCart: any = null; private selectedCatalogItem: any = null; + private selectedOrder: any = null; private detailQuantity = 1; private orderQueue: any[] = []; private orderQueueOpen = false; @@ -36,7 +37,7 @@ class FolkCartShop extends HTMLElement { private creatingPayment = false; private creatingGroupBuy = false; private _offlineUnsubs: (() => void)[] = []; - private _history = new ViewHistory<"carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "payments" | "group-buys">("carts"); + private _history = new ViewHistory<"carts" | "cart-detail" | "catalog" | "catalog-detail" | "orders" | "order-detail" | "payments" | "group-buys">("carts"); // Guided tour private _tour!: TourEngine; @@ -69,7 +70,7 @@ class FolkCartShop extends HTMLElement { // Read initial view from attribute (set by server routes) or URL params const initView = this.getAttribute("initial-view"); - if (initView && ["carts","catalog","orders","payments","group-buys","subscriptions"].includes(initView)) { + if (initView && ["carts","catalog","orders","order-detail","payments","group-buys","subscriptions"].includes(initView)) { this.view = initView as any; } const params = new URLSearchParams(window.location.search); @@ -243,14 +244,72 @@ class FolkCartShop extends HTMLElement { ]; this.orders = [ - { id: "demo-ord-1001", total_price: "30.00", currency: "USD", status: "paid", created_at: new Date(now - 2 * 86400000).toISOString(), artifact_title: "Order #1001", quantity: 2 }, - { id: "demo-ord-1002", total_price: "25.00", currency: "USD", status: "pending", created_at: new Date(now - 1 * 86400000).toISOString(), artifact_title: "Order #1002", quantity: 1 }, - { id: "demo-ord-1003", total_price: "23.00", currency: "USD", status: "shipped", created_at: new Date(now - 5 * 86400000).toISOString(), artifact_title: "Order #1003", quantity: 3 }, + { + id: "demo-ord-1001", total_price: "30.00", currency: "USD", status: "paid", + created_at: new Date(now - 2 * 86400000).toISOString(), artifact_title: "Order #1001", quantity: 2, + paid_at: new Date(now - 2 * 86400000 + 3600000).toISOString(), shipped_at: null, + items: [ + { title: "#DefectFi Tee", image_url: "/images/catalog/catalog-defectfi-tee.jpg", quantity: 1, unit_price: 25 }, + { title: "Cosmolocal Sticker Sheet", image_url: "/images/catalog/catalog-cosmolocal-stickers.jpg", quantity: 1, unit_price: 5 }, + ], + buyer: { name: "Alice", email: "alice@example.com" }, + payment: { method: "wallet", token: "USDC", chain_id: 8453, tx_hash: "0x7a3b9c1d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b" }, + provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" }, + }, + { + id: "demo-ord-1002", total_price: "25.00", currency: "USD", status: "pending", + created_at: new Date(now - 1 * 86400000).toISOString(), artifact_title: "Order #1002", quantity: 1, + paid_at: null, shipped_at: null, + items: [ + { title: "Cosmolocal Network Tee", image_url: "/images/catalog/catalog-cosmolocal-tee.jpg", quantity: 1, unit_price: 25 }, + ], + buyer: { name: "Bob", email: "bob@example.com" }, + payment: { method: "wallet", token: "USDC", chain_id: 8453, tx_hash: null }, + provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" }, + }, + { + id: "demo-ord-1003", total_price: "32.00", currency: "USD", status: "shipped", + created_at: new Date(now - 5 * 86400000).toISOString(), artifact_title: "Order #1003", quantity: 3, + paid_at: new Date(now - 5 * 86400000 + 1800000).toISOString(), shipped_at: new Date(now - 3 * 86400000).toISOString(), + items: [ + { title: "The Commons", image_url: "/images/catalog/catalog-the-commons.jpg", quantity: 2, unit_price: 12 }, + { title: "Doughnut Economics Zine", image_url: "/images/catalog/catalog-doughnut-economics.jpg", quantity: 1, unit_price: 8 }, + ], + buyer: { name: "Carol", email: "carol@example.com" }, + payment: { method: "wallet", token: "USDC", chain_id: 8453, tx_hash: "0x1122334455667788990011223344556677889900aabbccddeeff0011223344556" }, + provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" }, + }, + { + id: "demo-ord-1004", total_price: "28.00", currency: "USD", status: "paid", + created_at: new Date(now - 4 * 86400000).toISOString(), artifact_title: "Order #1004", quantity: 5, + paid_at: new Date(now - 4 * 86400000 + 7200000).toISOString(), shipped_at: null, + items: [ + { title: "rSpace Logo Patch", image_url: "/images/catalog/catalog-rspace-patch.jpg", quantity: 3, unit_price: 6 }, + { title: "Cosmolocal Vinyl Stickers", image_url: "/images/catalog/catalog-cosmolocal-vinyl-stickers.jpg", quantity: 2, unit_price: 5 }, + ], + buyer: { name: "Dave", email: "dave@example.com" }, + payment: { method: "wallet", token: "ETH", chain_id: 1, tx_hash: "0xaabbccdd11223344556677889900aabbccddeeff11223344556677889900aabb" }, + provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" }, + }, + { + id: "demo-ord-1005", total_price: "18.00", currency: "USD", status: "completed", + created_at: new Date(now - 10 * 86400000).toISOString(), artifact_title: "Order #1005", quantity: 1, + paid_at: new Date(now - 10 * 86400000 + 900000).toISOString(), shipped_at: new Date(now - 7 * 86400000).toISOString(), + items: [ + { title: "Mycelium Networks", image_url: "/images/catalog/catalog-mycelium-networks.jpg", quantity: 1, unit_price: 18 }, + ], + buyer: { name: "Eve", email: "eve@example.com" }, + payment: { method: "wallet", token: "USDC", chain_id: 11155111, tx_hash: "0xdeadbeef00112233445566778899aabbccddeeff00112233445566778899aabb" }, + provider: { name: "Community Print Co-op", city: "Portland, OR", turnaround: "5-7 business days" }, + }, ]; this.payments = [ - { id: "demo-pay-1", description: "Coffee tip", amount: "5.00", token: "USDC", chainId: 8453, recipientAddress: "0x1234...abcd", status: "paid", paymentMethod: "wallet", txHash: "0xabc123...", created_at: new Date(now - 1 * 86400000).toISOString(), paid_at: new Date(now - 1 * 86400000).toISOString() }, - { id: "demo-pay-2", description: "Invoice #42", amount: "25.00", token: "USDC", chainId: 8453, recipientAddress: "0x1234...abcd", status: "pending", paymentMethod: null, txHash: null, created_at: new Date(now - 3600000).toISOString(), paid_at: null }, + { id: "demo-pay-1", description: "Order #1001 — #DefectFi Tee + stickers", amount: "30.00", token: "USDC", chainId: 8453, recipientAddress: "0x7a3b...9a0b", status: "paid", paymentMethod: "wallet", txHash: "0x7a3b9c1d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b", created_at: new Date(now - 2 * 86400000).toISOString(), paid_at: new Date(now - 2 * 86400000 + 3600000).toISOString() }, + { id: "demo-pay-2", description: "Order #1003 — The Commons + zine", amount: "32.00", token: "USDC", chainId: 8453, recipientAddress: "0x1122...4556", status: "paid", paymentMethod: "wallet", txHash: "0x1122334455667788990011223344556677889900aabbccddeeff0011223344556", created_at: new Date(now - 5 * 86400000).toISOString(), paid_at: new Date(now - 5 * 86400000 + 1800000).toISOString() }, + { id: "demo-pay-3", description: "Order #1004 — patches + vinyl stickers", amount: "28.00", token: "ETH", chainId: 1, recipientAddress: "0xaabb...aabb", status: "paid", paymentMethod: "wallet", txHash: "0xaabbccdd11223344556677889900aabbccddeeff11223344556677889900aabb", created_at: new Date(now - 4 * 86400000).toISOString(), paid_at: new Date(now - 4 * 86400000 + 7200000).toISOString() }, + { id: "demo-pay-4", description: "Coffee tip", amount: "5.00", token: "USDC", chainId: 8453, recipientAddress: "0x1234...abcd", status: "paid", paymentMethod: "wallet", txHash: "0xfeed1234abcd5678ef901234abcd5678ef901234abcd5678ef901234abcd5678", created_at: new Date(now - 1 * 86400000).toISOString(), paid_at: new Date(now - 1 * 86400000).toISOString() }, + { id: "demo-pay-5", description: "Invoice #42", amount: "25.00", token: "USDC", chainId: 8453, recipientAddress: "0x1234...abcd", status: "pending", paymentMethod: null, txHash: null, created_at: new Date(now - 3600000).toISOString(), paid_at: null }, ]; this.groupBuys = [ @@ -415,6 +474,25 @@ class FolkCartShop extends HTMLElement { } } + private async contributePay(cartId: string, amount: number, username: string) { + try { + const res = await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/contribute-pay`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ amount, username }), + }); + if (!res.ok) { + const err = await res.json(); + console.error("Failed to create payment:", err.error); + return; + } + const { payUrl } = await res.json(); + window.location.href = payUrl; + } catch (e) { + console.error("Failed to create contribution payment:", e); + } + } + // ── Main render ── private render() { @@ -431,6 +509,8 @@ class FolkCartShop extends HTMLElement { content = this.renderCatalog(); } else if (this.view === "catalog-detail") { content = this.renderCatalogDetail(); + } else if (this.view === "order-detail") { + content = this.renderOrderDetail(); } else if (this.view === "payments") { content = this.renderPayments(); } else if (this.view === "group-buys") { @@ -460,6 +540,7 @@ class FolkCartShop extends HTMLElement { if (!prev) return; this.view = prev.view; if (prev.view !== "cart-detail") this.selectedCartId = null; + if (prev.view !== "order-detail") this.selectedOrder = null; this.render(); } @@ -528,6 +609,13 @@ class FolkCartShop extends HTMLElement { this.contribute(this.selectedCartId, parseFloat(amtInput.value), nameInput?.value || 'Anonymous'); } }); + this.shadow.querySelector("[data-action='contribute-pay']")?.addEventListener("click", () => { + const amtInput = this.shadow.querySelector("[data-field='contrib-amount']") as HTMLInputElement; + const nameInput = this.shadow.querySelector("[data-field='contrib-name']") as HTMLInputElement; + if (amtInput?.value && this.selectedCartId) { + this.contributePay(this.selectedCartId, parseFloat(amtInput.value), nameInput?.value || 'Anonymous'); + } + }); // Payment request actions const newPaymentBtn = this.shadow.querySelector("[data-action='new-payment']"); @@ -557,6 +645,13 @@ class FolkCartShop extends HTMLElement { }); }); + // Order card clicks → detail view + this.shadow.querySelectorAll("[data-order-id]").forEach((el) => { + el.addEventListener("click", () => { + this.loadOrderDetail((el as HTMLElement).dataset.orderId!); + }); + }); + // Catalog card clicks → detail view this.shadow.querySelectorAll("[data-catalog-id]").forEach((el) => { el.addEventListener("click", () => { @@ -742,7 +837,12 @@ class FolkCartShop extends HTMLElement {
@@ -797,6 +897,16 @@ class FolkCartShop extends HTMLElement { this.render(); } + private loadOrderDetail(id: string) { + const order = this.orders.find((o: any) => o.id === id); + if (!order) return; + this.selectedOrder = order; + this._history.push(this.view); + this.view = "order-detail"; + this._history.push("order-detail"); + this.render(); + } + private buildDemoFulfillOptions(entry: any) { const basePrice = entry.price || 10; return { @@ -1072,21 +1182,105 @@ class FolkCartShop extends HTMLElement { } return `