feat(app-shop): local/on-request sales via DM (drop shipping claims)

- Remove the 'Shipped worldwide' and 'At cost, no markup theatre' chips; the
  remaining chips are Lightning, Cashu, Local handover.
- Copy now says sold locally, on request: message me -> we sort the details ->
  pay in sats at handover. No shipping, at all.
- 'How it'll work' -> 'How buying works' (it's a DM, not a cart): Message me /
  We sort it out / Pay in sats.
- New 'Request an item' CTA in the hero that deep-links into a private DM with
  the shop owner (/messages/<PLATFORM_OWNER_PUBKEYS[0] npub>), so a buyer can
  start the conversation with zero checkout machinery.
- Honesty footer states local handover only / no shipping of any kind.
- AGENTS.md documents the model (no cart/checkout/shipping without a decision).
This commit is contained in:
SkateSpots 2026-09-27 21:52:00 +07:00
parent 8dcc97fd81
commit 059d9da5c4
2 changed files with 61 additions and 16 deletions

View file

@ -345,7 +345,12 @@ session with **no other memory of this project**. So:
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
paid in **sats only (Lightning or Cashu)** — no cards/accounts — sold
**locally, on request**: there is **no cart, no checkout, no shipping**. Buying
is a conversation: "Request an item" deep-links to a **DM with the owner**
(`/messages/<owner npub>`, owner = `PLATFORM_OWNER_PUBKEYS[0]`), the details
are agreed in chat, and sats change hands at handover. Don't add shipping
copy/rates or a cart without a decision to change that model. 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

View file

@ -1,21 +1,27 @@
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import {
ShoppingBag,
Zap,
Coins,
Truck,
Shirt,
Sticker,
Package,
Hammer,
Sparkles,
BadgeCheck,
MapPin,
MessageCircle,
ArrowRight,
} from 'lucide-react';
import { PageHeader } from '@/components/PageHeader';
import { NewsletterSubscribeButton } from '@/components/NewsletterSubscribeButton';
import { Button } from '@/components/ui/button';
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
/** Where "request an item" goes: an encrypted DM straight to the shop owner. */
const SHOP_OWNER_NPUB = nip19.npubEncode(PLATFORM_OWNER_PUBKEYS[0]);
const REQUEST_HREF = `/messages/${SHOP_OWNER_NPUB}`;
/**
* App Shop — SkateSpots merch.
@ -38,8 +44,7 @@ const PLANNED = [
const PAY_CHIPS = [
{ icon: Zap, label: 'Lightning' },
{ icon: Coins, label: 'Cashu' },
{ icon: Truck, label: 'Shipped worldwide' },
{ icon: BadgeCheck, label: 'At cost, no markup theatre' },
{ icon: MapPin, label: 'Local handover' },
];
export default function AppShopPage() {
@ -81,12 +86,30 @@ export default function AppShopPage() {
</h2>
<p className="mt-4 max-w-xl text-sm leading-relaxed text-gray-300 sm:text-base">
Merch for the people who keep this thing rolling. Paid in{' '}
<strong className="font-bold text-[#f7931a]">sats</strong> — Lightning or Cashu,
whichever you already keep. No cards, no accounts, no middleman skimming the drop.
Merch for the people who keep this thing rolling — sold{' '}
<strong className="font-bold text-white">locally, on request</strong>. You message
me, we sort the details, and you pay in{' '}
<strong className="font-bold text-[#f7931a]">sats</strong> — Lightning or Cashu —
when you collect it. No cards, no accounts, no checkout robot, no middleman
skimming the drop.
</p>
<div className="mt-6 flex flex-wrap gap-2">
<div className="mt-6 flex flex-wrap items-center gap-3">
<Button
asChild
className="gap-2 bg-[#ccff00] font-black uppercase text-black hover:bg-[#ccff00]/90"
>
<Link to={REQUEST_HREF}>
<MessageCircle className="h-4 w-4" />
Request an item
</Link>
</Button>
<span className="text-[11px] text-gray-500">
Opens a private, encrypted DM with me.
</span>
</div>
<div className="mt-5 flex flex-wrap gap-2">
{PAY_CHIPS.map(({ icon: Icon, label }) => (
<span
key={label}
@ -158,16 +181,31 @@ export default function AppShopPage() {
<section className="mt-10">
<div className="mb-4 flex items-center gap-3">
<h3 className="text-sm font-black uppercase tracking-widest text-white">
How it'll work
How buying works
</h3>
<div className="h-px flex-1 bg-white/10" />
<span className="text-[10px] font-bold uppercase tracking-widest text-gray-500">
it's a DM, not a cart
</span>
</div>
<ol className="grid gap-3 sm:grid-cols-3">
{[
{ 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.' },
{
n: '1',
t: 'Message me',
d: 'Send a DM with what you want — item, size, colour, whatever you are after.',
},
{
n: '2',
t: 'We sort it out',
d: 'Price in sats, and where and when to meet. It is a conversation, not a checkout.',
},
{
n: '3',
t: 'Pay in sats',
d: 'Lightning or Cashu at handover, then it is yours. No shipping, no waiting rooms.',
},
].map((s) => (
<li
key={s.n}
@ -211,9 +249,11 @@ export default function AppShopPage() {
<section className="mt-8 rounded-2xl border border-white/8 bg-white/[0.02] p-4">
<p className="text-xs leading-relaxed text-gray-500">
<strong className="text-gray-300">The honest bit:</strong> 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.
nothing here is for sale. There are no pre-orders, no countdown timers and no fake
scarcity — and it is <strong className="text-gray-300">local handover only</strong>,
arranged by DM, so there is no shipping of any kind. When there is something real,
this page will say so and we will sort the details between us. Until then it is
just an empty shop with good lighting.
</p>
<div className="mt-4 flex flex-wrap gap-2">
<Button asChild variant="outline" className="gap-2 border-white/15">