From 8dcc97fd81167fecc67b985f7bf1be2fabf7333e Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sun, 27 Sep 2026 21:40:41 +0700 Subject: [PATCH] feat(app-shop): add the App Shop section (empty on purpose) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New /app-shop page for SkateSpots merch, to be paid in sats only (Lightning or Cashu) — no cards, no accounts, no middleman. Design: gradient/glow hero ('Wear the scene' + Coming soon badge), an honest stock strip (0 items / None pre-orders / Nothing for sale today), an 'In the works' teaser grid (tees, decks, stickers, tools — dashed + 'soon', no prices), a three-step 'How it'll work' (pick -> pay in sats -> ships), a newsletter notify band (the drop lands there first) and a plain-English honesty footer. Deliberately contains no items: no stock, no prices, no pre-orders, no buy buttons. AGENTS.md documents that rule so nobody invents a catalogue later. Wiring: registered in NAV_CATALOG as id 'app-shop' (appears in the desktop 'More' menu AND the mobile burger menu automatically), lazy route in AppRouter, and added to the smoke-test route list so a broken page aborts deploys. Verified: page renders desktop + mobile (all sections), listed in both navs, smoke PASS with /app-shop. --- AGENTS.md | 11 ++ release-notes.txt | 7 + scripts/smoke-test.mjs | 1 + src/AppRouter.tsx | 2 + src/hooks/useNavPreferences.ts | 2 + src/pages/AppShopPage.tsx | 233 +++++++++++++++++++++++++++++++++ 6 files changed, 256 insertions(+) create mode 100644 src/pages/AppShopPage.tsx diff --git a/AGENTS.md b/AGENTS.md index 7e97ff8..7d16ef9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -344,6 +344,17 @@ session with **no other memory of this project**. So: burger menu (`MOBILE_MENU_LISTED_HREFS`) now **appends catalog items that aren't hard-coded**, so Marketplace/Mints (and any future section) are always reachable on mobile — don't re-hardcode only a subset. +- **App Shop** (`/app-shop`, `src/pages/AppShopPage.tsx`): SkateSpots merch, to be + paid in **sats only (Lightning or Cashu)** — no cards/accounts. Registered in + `NAV_CATALOG` as id `app-shop` (label "App Shop", `ShoppingBag`), which is what + makes it appear in **both** the desktop "More" menu and the mobile burger menu + (the mobile menu appends catalog items not in `MOBILE_MENU_LISTED_HREFS`). It is + **intentionally empty**: no stock, no prices, no pre-orders and nothing for + sale — the copy states that plainly ("an empty shop with good lighting") and + only previews what's coming. **Do not add prices/fake urgency/buy buttons until + there is real stock**; when items exist, model them as listings and take + Lightning/Cashu payment. The page reads the live stock strip as 0/None/Nothing + — keep it honest if that changes. - **Mints directory**: `/mints` page lists Cashu mints from the community **MintRadar** project. MintRadar has **no CORS**, so it's fetched same-origin at `/api/mints/*` — prod proxies via `public/_redirects` (`/api/mints/* → diff --git a/release-notes.txt b/release-notes.txt index 3cc1b29..a68dd81 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -1,5 +1,12 @@ v1.5.4 +🛍 App Shop — a new section (empty on purpose) +- Merch is coming, and it'll be paid in sats: Lightning or Cashu, your choice. + No cards, no accounts, no processor skimming the drop. +- To be clear: we hold no stock and nothing is for sale yet. No pre-orders, no + countdown timers, no fake scarcity — the page says so, and previews what's in + the works. Subscribe to the newsletter to get the drop first. + ✉️ Direct messages actually arrive now - Your DM inbox relays were being forgotten every time the app loaded, so messages were sent to your normal relays instead of the ones the other person diff --git a/scripts/smoke-test.mjs b/scripts/smoke-test.mjs index eeebce0..cf9ac8c 100755 --- a/scripts/smoke-test.mjs +++ b/scripts/smoke-test.mjs @@ -140,6 +140,7 @@ const ROUTES = [ '/collections', '/stats', '/mints', + '/app-shop', '/messages/group/abc123', ]; const problems = []; diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 0864140..174e560 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -17,6 +17,7 @@ const EventsPage = lazyWithRetry(() => import("./pages/EventsPage")); const LabPage = lazyWithRetry(() => import("./pages/LabPage")); const WalletPage = lazyWithRetry(() => import("./pages/WalletPage")); const MintsPage = lazyWithRetry(() => import("./pages/MintsPage")); +const AppShopPage = lazyWithRetry(() => import("./pages/AppShopPage")); const SettingsPage = lazyWithRetry(() => import("./pages/SettingsPage")); const CreateSpotPage = lazyWithRetry(() => import("./pages/CreateSpotPage")); const CreateShopPage = lazyWithRetry(() => import("./pages/CreateShopPage")); @@ -134,6 +135,7 @@ export function AppRouter() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/hooks/useNavPreferences.ts b/src/hooks/useNavPreferences.ts index 3e6a5d5..7e78056 100644 --- a/src/hooks/useNavPreferences.ts +++ b/src/hooks/useNavPreferences.ts @@ -11,6 +11,7 @@ import { BarChart3, Coins, Handshake, + ShoppingBag, type LucideIcon, } from 'lucide-react'; import { useLocalStorage } from '@/hooks/useLocalStorage'; @@ -60,6 +61,7 @@ export const NAV_CATALOG: NavTab[] = [ { id: 'crews', href: '/crews', labelKey: 'nav.crews', label: 'Crews', icon: Users }, { id: 'wallet', href: '/wallet', labelKey: 'nav.wallet', label: 'Wallet', icon: Wallet, authOnly: true }, { id: 'mints', href: '/mints', label: 'Mints', icon: Coins }, + { id: 'app-shop', href: '/app-shop', label: 'App Shop', icon: ShoppingBag }, { id: 'trades', href: '/marketplace', label: 'Marketplace', icon: Handshake }, { id: 'stash', href: '/stash', label: 'Stash', icon: Package, authOnly: true }, { id: 'collections', href: '/collections', label: 'Collections', icon: Bookmark, authOnly: true }, diff --git a/src/pages/AppShopPage.tsx b/src/pages/AppShopPage.tsx new file mode 100644 index 0000000..5d2d03c --- /dev/null +++ b/src/pages/AppShopPage.tsx @@ -0,0 +1,233 @@ +import { useSeoMeta } from '@unhead/react'; +import { Link } from 'react-router-dom'; +import { + ShoppingBag, + Zap, + Coins, + Truck, + Shirt, + Sticker, + Package, + Hammer, + Sparkles, + BadgeCheck, + ArrowRight, +} from 'lucide-react'; +import { PageHeader } from '@/components/PageHeader'; +import { NewsletterSubscribeButton } from '@/components/NewsletterSubscribeButton'; +import { Button } from '@/components/ui/button'; + +/** + * App Shop — SkateSpots merch. + * + * Deliberately a STOREFRONT WITH NOTHING IN IT: there is no stock, no + * pre-order, no price and nothing to buy. It exists so the section, the design + * language and the payment story (sats only — Lightning or Cashu) are in place + * before the first drop. When items land we can model them as listings and take + * Lightning/Cashu payment; until then this page must never imply a sale. + */ + +/** What we're working on. Not products, not prices — just intent. */ +const PLANNED = [ + { icon: Shirt, label: 'Tees & hoodies', note: 'Heavyweight, printed somewhere that pays people properly.' }, + { icon: Package, label: 'Decks', note: 'Small numbered runs. Ride it or hang it, your call.' }, + { icon: Sticker, label: 'Sticker packs', note: 'For the box, the rail, the laptop lid.' }, + { icon: Hammer, label: 'Shop tools', note: 'The hardware the homies actually wear out.' }, +]; + +const PAY_CHIPS = [ + { icon: Zap, label: 'Lightning' }, + { icon: Coins, label: 'Cashu' }, + { icon: Truck, label: 'Shipped worldwide' }, + { icon: BadgeCheck, label: 'At cost, no markup theatre' }, +]; + +export default function AppShopPage() { + useSeoMeta({ + title: 'App Shop — SkateSpots', + description: + 'SkateSpots merch, paid in sats over Lightning or Cashu. Nothing in stock yet — here is what is coming.', + }); + + return ( +
+ + + {/* ── Hero ─────────────────────────────────────────────────────────── */} +
+
+
+ +
+ + + Coming soon + + +

+ + Wear the + +
+ Scene +

+ +

+ Merch for the people who keep this thing rolling. Paid in{' '} + sats — Lightning or Cashu, + whichever you already keep. No cards, no accounts, no middleman skimming the drop. +

+ +
+ {PAY_CHIPS.map(({ icon: Icon, label }) => ( + + + {label} + + ))} +
+
+
+ + {/* ── Honest stock strip ───────────────────────────────────────────── */} +
+ {[ + { k: 'Items in stock', v: '0' }, + { k: 'Pre-orders', v: 'None' }, + { k: 'For sale today', v: 'Nothing' }, + ].map((s) => ( +
+

{s.v}

+

+ {s.k} +

+
+ ))} +
+ + {/* ── What's in the works ──────────────────────────────────────────── */} +
+
+

+ In the works +

+
+ + not for sale yet + +
+ +
+ {PLANNED.map(({ icon: Icon, label, note }) => ( +
+
+ + + +
+

{label}

+

{note}

+
+
+ + soon + +
+ ))} +
+
+ + {/* ── How it'll work ───────────────────────────────────────────────── */} +
+
+

+ How it'll work +

+
+
+ +
    + {[ + { n: '1', t: 'Pick your thing', d: 'One page, no logged-in pricing games, no fake urgency.' }, + { n: '2', t: 'Pay in sats', d: 'Lightning or Cashu. Straight to us — no processor in the middle.' }, + { n: '3', t: 'It ships', d: 'Posted to your door with an honest tracking number.' }, + ].map((s) => ( +
  1. + + {s.n} + +

    {s.t}

    +

    {s.d}

    +
  2. + ))} +
+
+ + {/* ── Notify band ──────────────────────────────────────────────────── */} +
+

+ Want the drop first? +

+

+ The newsletter is where merch lands first — sizes, colours, and the real + price in sats. Subscribe and it reaches your notification bell. +

+
+ +
+

+ Got a design you want on a deck?{' '} + + Tell us in the community section + + . +

+
+ + {/* ── Honesty foot ─────────────────────────────────────────────────── */} +
+

+ The honest bit: we hold no stock and + nothing here is for sale. There are no pre-orders, no countdown timers and no + fake scarcity — when there is something real to buy, this page will say so and + take your sats. Until then it is just an empty shop with good lighting. +

+
+ + +
+
+
+ ); +}