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:
parent
8dcc97fd81
commit
059d9da5c4
2 changed files with 61 additions and 16 deletions
|
|
@ -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
|
aren't hard-coded**, so Marketplace/Mints (and any future section) are always
|
||||||
reachable on mobile — don't re-hardcode only a subset.
|
reachable on mobile — don't re-hardcode only a subset.
|
||||||
- **App Shop** (`/app-shop`, `src/pages/AppShopPage.tsx`): SkateSpots merch, to be
|
- **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
|
`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
|
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
|
(the mobile menu appends catalog items not in `MOBILE_MENU_LISTED_HREFS`). It is
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,27 @@
|
||||||
import { useSeoMeta } from '@unhead/react';
|
import { useSeoMeta } from '@unhead/react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { nip19 } from 'nostr-tools';
|
||||||
import {
|
import {
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
Zap,
|
Zap,
|
||||||
Coins,
|
Coins,
|
||||||
Truck,
|
|
||||||
Shirt,
|
Shirt,
|
||||||
Sticker,
|
Sticker,
|
||||||
Package,
|
Package,
|
||||||
Hammer,
|
Hammer,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
BadgeCheck,
|
MapPin,
|
||||||
|
MessageCircle,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { PageHeader } from '@/components/PageHeader';
|
import { PageHeader } from '@/components/PageHeader';
|
||||||
import { NewsletterSubscribeButton } from '@/components/NewsletterSubscribeButton';
|
import { NewsletterSubscribeButton } from '@/components/NewsletterSubscribeButton';
|
||||||
import { Button } from '@/components/ui/button';
|
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.
|
* App Shop — SkateSpots merch.
|
||||||
|
|
@ -38,8 +44,7 @@ const PLANNED = [
|
||||||
const PAY_CHIPS = [
|
const PAY_CHIPS = [
|
||||||
{ icon: Zap, label: 'Lightning' },
|
{ icon: Zap, label: 'Lightning' },
|
||||||
{ icon: Coins, label: 'Cashu' },
|
{ icon: Coins, label: 'Cashu' },
|
||||||
{ icon: Truck, label: 'Shipped worldwide' },
|
{ icon: MapPin, label: 'Local handover' },
|
||||||
{ icon: BadgeCheck, label: 'At cost, no markup theatre' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function AppShopPage() {
|
export default function AppShopPage() {
|
||||||
|
|
@ -81,12 +86,30 @@ export default function AppShopPage() {
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p className="mt-4 max-w-xl text-sm leading-relaxed text-gray-300 sm:text-base">
|
<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{' '}
|
Merch for the people who keep this thing rolling — sold{' '}
|
||||||
<strong className="font-bold text-[#f7931a]">sats</strong> — Lightning or Cashu,
|
<strong className="font-bold text-white">locally, on request</strong>. You message
|
||||||
whichever you already keep. No cards, no accounts, no middleman skimming the drop.
|
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>
|
</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 }) => (
|
{PAY_CHIPS.map(({ icon: Icon, label }) => (
|
||||||
<span
|
<span
|
||||||
key={label}
|
key={label}
|
||||||
|
|
@ -158,16 +181,31 @@ export default function AppShopPage() {
|
||||||
<section className="mt-10">
|
<section className="mt-10">
|
||||||
<div className="mb-4 flex items-center gap-3">
|
<div className="mb-4 flex items-center gap-3">
|
||||||
<h3 className="text-sm font-black uppercase tracking-widest text-white">
|
<h3 className="text-sm font-black uppercase tracking-widest text-white">
|
||||||
How it'll work
|
How buying works
|
||||||
</h3>
|
</h3>
|
||||||
<div className="h-px flex-1 bg-white/10" />
|
<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>
|
</div>
|
||||||
|
|
||||||
<ol className="grid gap-3 sm:grid-cols-3">
|
<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: '1',
|
||||||
{ n: '3', t: 'It ships', d: 'Posted to your door with an honest tracking number.' },
|
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) => (
|
].map((s) => (
|
||||||
<li
|
<li
|
||||||
key={s.n}
|
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">
|
<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">
|
<p className="text-xs leading-relaxed text-gray-500">
|
||||||
<strong className="text-gray-300">The honest bit:</strong> we hold no stock and
|
<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
|
nothing here is for sale. There are no pre-orders, no countdown timers and no fake
|
||||||
fake scarcity — when there is something real to buy, this page will say so and
|
scarcity — and it is <strong className="text-gray-300">local handover only</strong>,
|
||||||
take your sats. Until then it is just an empty shop with good lighting.
|
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>
|
</p>
|
||||||
<div className="mt-4 flex flex-wrap gap-2">
|
<div className="mt-4 flex flex-wrap gap-2">
|
||||||
<Button asChild variant="outline" className="gap-2 border-white/15">
|
<Button asChild variant="outline" className="gap-2 border-white/15">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue