SkateSpots/src/pages/AppShopPage.tsx

318 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import {
ShoppingBag,
Zap,
Coins,
Shirt,
Sticker,
Package,
Hammer,
Sparkles,
MapPin,
MessageCircle,
Printer,
Download,
Scissors,
} from 'lucide-react';
import { PageHeader } from '@/components/PageHeader';
import { Button } from '@/components/ui/button';
import { useTranslation } from '@/contexts/language';
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.
*
* 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.
*/
/**
* Free, printable artwork. Physical size is baked into the files (mm units), so
* "Actual size" printing gives an exact 55 × 86 mm sticker / A3 poster, and the
* QR in each one resolves to https://skate-spots.org.
*/
const FREE_PRINT = [
{
titleKey: 'walletUi.appShop.sticker',
size: '55 × 86 mm',
tipKey: 'walletUi.appShop.stickerTip',
file: '/images/print/skatespots-sticker.svg',
},
{
titleKey: 'walletUi.appShop.poster',
size: 'A3 · 297 × 420 mm',
tipKey: 'walletUi.appShop.posterTip',
file: '/images/print/skatespots-poster.svg',
},
];
/** What we're working on. Not products, not prices — just intent. */
const PLANNED = [
{ icon: Shirt, labelKey: 'walletUi.appShop.tees' },
{ icon: Package, labelKey: 'walletUi.appShop.decks' },
// 'Sticker packs' is rendered separately in the grid, because it is the one
// entry that already contains something real: the free printables.
{ icon: Hammer, labelKey: 'walletUi.appShop.shopTools' },
];
const PAY_CHIPS = [
{ icon: Zap, labelKey: 'walletUi.appShop.lightning' },
{ icon: Coins, labelKey: 'walletUi.appShop.cashu' },
{ icon: MapPin, labelKey: 'walletUi.appShop.localHandover' },
];
export default function AppShopPage() {
const { t } = useTranslation();
useSeoMeta({
title: t('seoUi.appShopTitle'),
description:
'SkateSpots merch, paid in sats over Lightning or Cashu. Nothing in stock yet — here is what is coming.',
});
return (
<div className="container mx-auto max-w-4xl px-4 py-8 pb-24 md:pb-14">
<PageHeader
icon={ShoppingBag}
title={t('walletUi.appShop.title')}
accent="#ccff00"
subtitle={t('walletUi.appShop.subtitle')}
/>
{/* ── Hero ─────────────────────────────────────────────────────────── */}
<section className="relative overflow-hidden rounded-3xl border-2 border-[#ff0040]/25 bg-gradient-to-br from-[#ff0040]/12 via-black to-[#ccff00]/[0.06] px-5 py-10 sm:px-8 sm:py-14">
<div className="pointer-events-none absolute -top-28 left-1/2 h-64 w-64 -translate-x-1/2 rounded-full bg-[#ff0040]/25 blur-3xl" />
<div className="pointer-events-none absolute -bottom-24 -right-16 h-56 w-56 rounded-full bg-[#ccff00]/15 blur-3xl" />
<div className="relative">
<span className="inline-flex items-center gap-2 rounded-full border border-[#ccff00]/40 bg-[#ccff00]/10 px-3 py-1 text-[10px] font-black uppercase tracking-widest text-[#ccff00]">
<Sparkles className="h-3 w-3" />
{t('walletUi.appShop.comingSoon')}
</span>
<h2
className="mt-4 text-5xl font-black uppercase leading-[0.9] sm:text-7xl"
style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}
>
<span className="bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00] bg-clip-text text-transparent">
{t('walletUi.appShop.wearThe')}
</span>
<br />
<span className="text-white">{t('walletUi.appShop.scene')}</span>
</h2>
<p className="mt-4 max-w-xl text-sm leading-relaxed text-gray-300 sm:text-base">
{t('walletUi.appShop.heroDesc1')}{' '}
<strong className="font-bold text-white">{t('walletUi.appShop.locally')}</strong>{t('walletUi.appShop.heroDesc2')}{' '}
<strong className="font-bold text-[#f7931a]">sats</strong>{t('walletUi.appShop.heroDesc3')}
</p>
<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" />
{t('walletUi.appShop.requestItem')}
</Link>
</Button>
<span className="text-[11px] text-gray-500">
{t('walletUi.appShop.dmHint')}
</span>
</div>
<div className="mt-5 flex flex-wrap gap-2">
{PAY_CHIPS.map(({ icon: Icon, labelKey }) => (
<span
key={labelKey}
className="inline-flex items-center gap-1.5 rounded-full border border-white/12 bg-white/[0.04] px-3 py-1.5 text-[11px] font-bold text-gray-300 backdrop-blur-sm"
>
<Icon className="h-3.5 w-3.5 text-[#ccff00]" />
{t(labelKey)}
</span>
))}
</div>
</div>
</section>
{/* ── What's in the works ──────────────────────────────────────────── */}
<section className="mt-10">
<div className="mb-4 flex items-center gap-3">
<h3 className="text-sm font-black uppercase tracking-widest text-white">
{t('walletUi.appShop.inTheWorks')}
</h3>
<div className="h-px flex-1 bg-white/10" />
<span className="text-[10px] font-bold uppercase tracking-widest text-gray-500">
{t('walletUi.appShop.notForSale')}
</span>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{PLANNED.slice(0, 2).map(({ icon: Icon, labelKey }) => (
<div
key={labelKey}
className="group relative overflow-hidden rounded-2xl border-2 border-dashed border-white/12 bg-white/[0.02] p-4 transition-colors hover:border-[#ccff00]/30"
>
<div className="flex items-start gap-3">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[#ccff00]/10 text-[#ccff00]">
<Icon className="h-5 w-5" />
</span>
<p className="min-w-0 self-center font-black uppercase tracking-wide text-white">
{t(labelKey)}
</p>
</div>
<span className="absolute right-3 top-3 rounded-full border border-white/12 px-2 py-0.5 text-[9px] font-black uppercase tracking-widest text-gray-500">
{t('walletUi.appShop.soon')}
</span>
</div>
))}
{/* Sticker packs — the only thing you can actually GET today: the free
printables. Kept inside its category (not a separate section) so the
shop reads as one list of merch, free stuff included. */}
<div className="rounded-2xl border-2 border-dashed border-[#ccff00]/35 bg-[#ccff00]/[0.04] p-4 sm:col-span-2">
<div className="flex items-start gap-3">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[#ccff00]/10 text-[#ccff00]">
<Sticker className="h-5 w-5" />
</span>
<div className="min-w-0 flex-1">
<p className="font-black uppercase tracking-wide text-white">{t('walletUi.appShop.stickerPacks')}</p>
<p className="mt-0.5 text-xs leading-relaxed text-gray-400">
{t('walletUi.appShop.stickerPacksDesc')}
</p>
</div>
<span className="shrink-0 rounded-full bg-[#ccff00] px-2 py-0.5 text-[9px] font-black uppercase tracking-widest text-black">
{t('common.free')}
</span>
</div>
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">
{FREE_PRINT.map((item) => (
<div
key={item.titleKey}
className="overflow-hidden rounded-xl border border-white/10 bg-black/40"
>
<div className="flex h-36 items-center justify-center p-3">
<img
src={item.file}
alt={t('walletUi.appShop.printableAlt', { title: t(item.titleKey) })}
className="max-h-full max-w-full drop-shadow-[0_4px_14px_rgba(0,0,0,0.6)]"
/>
</div>
<div className="border-t border-white/8 p-3">
<div className="flex items-baseline justify-between gap-2">
<p className="font-bold uppercase tracking-wide text-white">{t(item.titleKey)}</p>
<span className="shrink-0 text-[10px] font-bold uppercase tracking-widest text-gray-500">
{item.size}
</span>
</div>
<p className="mt-1 flex items-start gap-1.5 text-[11px] leading-relaxed text-gray-500">
<Scissors className="mt-0.5 h-3 w-3 shrink-0 text-gray-600" />
{t(item.tipKey)}
</p>
<div className="mt-2.5 flex flex-wrap gap-2">
<Button
asChild
size="sm"
className="gap-1.5 bg-[#ccff00] font-bold text-black hover:bg-[#ccff00]/90"
>
<a href={item.file} target="_blank" rel="noopener noreferrer">
<Printer className="h-3.5 w-3.5" /> {t('walletUi.appShop.print')}
</a>
</Button>
<Button asChild size="sm" variant="outline" className="gap-1.5 border-white/15">
<a href={item.file} download>
<Download className="h-3.5 w-3.5" /> {t('common.save')}
</a>
</Button>
</div>
</div>
</div>
))}
</div>
<p className="mt-3 text-[11px] leading-relaxed text-gray-600">
{t('walletUi.appShop.printNote1')} <strong className="text-gray-400">{t('walletUi.appShop.actualSize')}</strong> {t('walletUi.appShop.printNote2')}
</p>
</div>
{PLANNED.slice(2).map(({ icon: Icon, labelKey }) => (
<div
key={labelKey}
className="group relative overflow-hidden rounded-2xl border-2 border-dashed border-white/12 bg-white/[0.02] p-4 transition-colors hover:border-[#ccff00]/30"
>
<div className="flex items-start gap-3">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[#ccff00]/10 text-[#ccff00]">
<Icon className="h-5 w-5" />
</span>
<p className="min-w-0 self-center font-black uppercase tracking-wide text-white">
{t(labelKey)}
</p>
</div>
<span className="absolute right-3 top-3 rounded-full border border-white/12 px-2 py-0.5 text-[9px] font-black uppercase tracking-widest text-gray-500">
{t('walletUi.appShop.soon')}
</span>
</div>
))}
</div>
</section>
{/* ── How it'll work ───────────────────────────────────────────────── */}
<section className="mt-10">
<div className="mb-4 flex items-center gap-3">
<h3 className="text-sm font-black uppercase tracking-widest text-white">
{t('walletUi.appShop.howBuying')}
</h3>
<div className="h-px flex-1 bg-white/10" />
<span className="text-[10px] font-bold uppercase tracking-widest text-gray-500">
{t('walletUi.appShop.dmNotCart')}
</span>
</div>
<ol className="grid gap-3 sm:grid-cols-3">
{[
{
n: '1',
tk: 'walletUi.appShop.step1Title',
dk: 'walletUi.appShop.step1Desc',
},
{
n: '2',
tk: 'walletUi.appShop.step2Title',
dk: 'walletUi.appShop.step2Desc',
},
{
n: '3',
tk: 'walletUi.appShop.step3Title',
dk: 'walletUi.appShop.step3Desc',
},
].map((s) => (
<li
key={s.n}
className="rounded-2xl border border-white/8 bg-white/[0.03] p-4"
>
<span
className="mb-3 flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-[#ff0040] to-[#ff6600] text-sm font-black text-white"
style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}
>
{s.n}
</span>
<p className="font-black uppercase tracking-wide text-white">{t(s.tk)}</p>
<p className="mt-1 text-xs leading-relaxed text-gray-400">{t(s.dk)}</p>
</li>
))}
</ol>
</section>
</div>
);
}