feat(seo): add OG/Twitter meta tags + generated OG banner image

- Add og:title, og:description, og:image, twitter:card meta tags to landing page
- Add default OG tags to renderShell for all module pages
- Generate 1024x576 OG banner image via fal.ai Flux Pro (dark indigo + network pattern)
- Update meta description: "rSpace is a local-first platform where communities own their tools, data, and governance"
- Update page title to "rSpace — Own Your Community Infrastructure"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-11 16:51:25 -07:00
parent 636fc133fe
commit 602544ecdf
4 changed files with 103 additions and 2 deletions

View File

@ -0,0 +1,75 @@
---
id: TASK-109
title: QR Code Payment Requests for rCart
status: Done
assignee: []
created_date: '2026-03-11 23:43'
labels:
- rcart
- payments
- QR
- crypto
dependencies: []
references:
- modules/rcart/mod.ts
- modules/rcart/schemas.ts
- modules/rcart/components/folk-payment-page.ts
- modules/rcart/components/folk-payment-request.ts
- shared/transak.ts
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add shareable QR code payment system to rCart. When scanned, the QR opens a public payment page where anyone can pay via credit card (Transak), external wallet (MetaMask via EIP-6963), or EncryptID passkey-derived EOA.
Includes:
- PaymentRequestDoc schema with payment type, inventory limits, enabled methods
- 7 API endpoints (create, list, get, update status, QR SVG, Transak session, page routes)
- folk-payment-page.ts: payer-facing 3-tab payment page (Card/Wallet/EncryptID)
- folk-payment-request.ts: self-service QR generator with passkey auth
- Payment type: single (one-time) or subscription (reusable QR, accepts multiple payments)
- Inventory limits: maxPayments cap with auto-fill status when limit reached
- Payment method toggles: enable/disable card/wallet/encryptid per payment request
- Extracted shared Transak utilities to shared/transak.ts
- publicWrite on cartModule for public payment page access
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 POST /api/payments creates payment request with all fields
- [ ] #2 GET /pay/:id renders public payment page with enabled tabs only
- [ ] #3 GET /request renders self-service QR generator with passkey auth
- [ ] #4 GET /api/payments/:id/qr returns SVG QR code
- [ ] #5 Payment type toggle: single vs subscription
- [ ] #6 Inventory limit: maxPayments with auto-fill when reached
- [ ] #7 Payment method toggles: card/wallet/encryptid per request
- [ ] #8 Wallet tab: EIP-6963 discovery + ERC-20/ETH transfer
- [ ] #9 Card tab: Transak iframe integration
- [ ] #10 EncryptID tab: passkey + viem signing
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented full QR code payment request system for rCart across two sessions:
**Session 1** — Core implementation:
- Created PaymentRequestDoc schema with Automerge CRDT storage
- Added 7 API routes: create, list, get, status update, QR SVG, Transak session, page routes
- Built folk-payment-page.ts (payer-facing, 3 tabs: Card/Wallet/EncryptID)
- Built folk-payment-request.ts (self-service QR generator with passkey auth)
- Extracted Transak utils to shared/transak.ts, updated rFlows import
- Added amountEditable support for tip/donation use cases
**Session 2** — Enhancements + 403 fix:
- Fixed 403 "write access required" by adding publicWrite to cartModule
- Added paymentType: 'single' | 'subscription' toggle
- Added maxPayments inventory limit with paymentCount tracking + 'filled' status
- Added enabledMethods toggles (card/wallet/encryptid) per payment request
- Payment page only renders enabled tabs, shows inventory progress bar
- Subscriptions reset to pending after each payment until filled
Commits: 636fc13, deployed to production.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -31,8 +31,26 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/favicon.png">
<title>rSpace Community Platform</title>
<meta name="description" content="A collaborative, local-first community platform with 22+ interoperable tools. Design global, manufacture local.">
<link rel="apple-touch-icon" href="/logo.png">
<title>rSpace Own Your Community Infrastructure</title>
<meta name="description" content="rSpace is a local-first platform where communities own their tools, data, and governance. 25+ composable apps — from voting to budgets to maps — encrypted, interoperable, and yours.">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://rspace.online">
<meta property="og:title" content="rSpace — Own Your Community Infrastructure">
<meta property="og:description" content="rSpace is a local-first platform where communities own their tools, data, and governance. 25+ composable apps — from voting to budgets to maps — encrypted, interoperable, and yours.">
<meta property="og:image" content="https://rspace.online/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:site_name" content="rSpace">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="rSpace — Own Your Community Infrastructure">
<meta name="twitter:description" content="Local-first community platform. 25+ composable apps — voting, budgets, maps, payments, identity — encrypted and self-sovereign.">
<meta name="twitter:image" content="https://rspace.online/og-image.png">
<script>(function(){var t=localStorage.getItem('canvas-theme');if(!t)t=matchMedia('(prefers-color-scheme:light)').matches?'light':'dark';document.documentElement.setAttribute('data-theme',t)})()</script>
<link rel="stylesheet" href="/theme.css">
<link rel="stylesheet" href="/shell.css">

View File

@ -111,6 +111,14 @@ export function renderShell(opts: ShellOptions): string {
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>${escapeHtml(title)}</title>
<meta name="description" content="rSpace — local-first community platform with 25+ composable apps. Encrypted, interoperable, self-sovereign.">
<meta property="og:type" content="website">
<meta property="og:title" content="${escapeAttr(title)}">
<meta property="og:description" content="rSpace — local-first community platform with 25+ composable apps. Encrypted, interoperable, self-sovereign.">
<meta property="og:image" content="https://rspace.online/og-image.png">
<meta property="og:site_name" content="rSpace">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://rspace.online/og-image.png">
<script>(function(){var t=localStorage.getItem('canvas-theme');if(!t)t=matchMedia('(prefers-color-scheme:light)').matches?'light':'dark';document.documentElement.setAttribute('data-theme',t);var b=localStorage.getItem('canvas-bg')||'grid';document.documentElement.setAttribute('data-canvas-bg',b)})()</script>
<link rel="stylesheet" href="/theme.css">
<link rel="stylesheet" href="/shell.css">

BIN
website/public/og-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB