/** * VerifyCardPage — /verify/:naddr * * VERIFIER-FIRST: a door person scans a QR (or taps an NFC tag) and needs one * answer in half a second — ✅ / ❌ — then the facts. The technical detail is * tucked below. No account, no app needed to verify. * * It is deliberately honest about what it does and does NOT prove. */ import { useParams, useSearchParams, Link } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { nip19, verifyEvent } from 'nostr-tools'; import { useSeoMeta } from '@unhead/react'; import { BadgeCheck, ShieldAlert, ShieldX, Loader2, ExternalLink } from 'lucide-react'; import { useNostr } from '@/hooks/useNostr'; import { useAuthor } from '@/hooks/useAuthor'; import { useTranslation } from '@/contexts/language'; import { genUserName } from '@/lib/genUserName'; import { RIDER_CARD_KIND, PARK_ACCESS_CARD_KIND, passExpiry } from '@/lib/riderCard'; import { SKATE_SPOT_KIND, SKATE_SHOP_KIND, SKATE_CREW_KIND, CALENDAR_EVENT_KIND, CALENDAR_RSVP_KIND } from '@/lib/types'; import type { NostrEvent } from '@nostrify/nostrify'; interface ParsedNaddr { kind: number; pubkey: string; identifier: string; relays: string[]; } function parseNaddr(value: string): ParsedNaddr | null { try { const d = nip19.decode(value); if (d.type !== 'naddr') return null; const { kind, pubkey, identifier, relays } = d.data; return { kind, pubkey, identifier, relays: relays ?? [] }; } catch { return null; } } function shortNpub(hex: string): string { if (!hex) return '—'; try { const n = nip19.npubEncode(hex); return `${n.slice(0, 12)}…${n.slice(-6)}`; } catch { return hex.slice(0, 12); } } type Tone = 'good' | 'warn' | 'bad'; export default function VerifyCardPage() { const { t } = useTranslation(); const { naddr = '' } = useParams<{ naddr: string }>(); const [searchParams] = useSearchParams(); const holderParam = searchParams.get('h') ?? ''; const holderHex = (() => { if (!holderParam) return ''; if (/^[0-9a-f]{64}$/i.test(holderParam)) return holderParam.toLowerCase(); try { const d = nip19.decode(holderParam); return d.type === 'npub' ? d.data : ''; } catch { return ''; } })(); const { nostr } = useNostr(); const parsed = parseNaddr(naddr); useSeoMeta({ title: 'Verify — SkateSpots', description: 'Verify a SkateSpots card or access pass.' }); const result = useQuery({ queryKey: ['verify-card', naddr], enabled: !!parsed, staleTime: 60_000, queryFn: async (c) => { if (!parsed) return { status: 'notfound' as const }; const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]); const events = await nostr.query( [{ kinds: [parsed.kind], authors: [parsed.pubkey], '#d': [parsed.identifier], limit: 1 }], { signal }, ); const event = events[0] as NostrEvent | undefined; if (!event) return { status: 'notfound' as const }; if (!verifyEvent(event)) return { status: 'invalid' as const }; // Access pass — confirm the issuer OWNS the target it names (a venue spot // 30818, or an event 31923 for a check-in pass). let venue: { status: 'none' | 'verified' | 'mismatch'; title?: string; coord?: string; kind?: number } = { status: 'none' }; if (event.kind === PARK_ACCESS_CARD_KIND) { const aTag = event.tags.find(([n]) => n === 'a')?.[1]; if (aTag) { venue = { status: 'mismatch', coord: aTag }; const [k, pk, dTag] = aTag.split(':'); const kindNum = Number(k); if ((kindNum === SKATE_SPOT_KIND || kindNum === SKATE_SHOP_KIND || kindNum === CALENDAR_EVENT_KIND) && pk && dTag) { try { const targets = await nostr.query( [{ kinds: [kindNum], authors: [pk], '#d': [dTag], limit: 1 }], { signal }, ); const target = targets[0] as NostrEvent | undefined; if (target) { venue = { status: target.pubkey === event.pubkey ? 'verified' : 'mismatch', title: target.tags.find(([n]) => n === 'title')?.[1] || target.tags.find(([n]) => n === 'name')?.[1], coord: aTag, kind: kindNum, }; } } catch { /* leave mismatch */ } } } } return { status: 'valid' as const, event, venue }; }, }); // Event ticket: the holder carries ?h= in the QR, so a door can confirm // they actually RSVP'd to the organiser's event. const ticket = useQuery<{ status: string }>({ queryKey: ['ticket-rsvp', naddr, holderHex], enabled: !!parsed && parsed.kind === CALENDAR_EVENT_KIND && !!holderHex, queryFn: async (c) => { if (!parsed || !holderHex) return { status: 'none' }; const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]); const coord = `${parsed.kind}:${parsed.pubkey}:${parsed.identifier}`; const events = await nostr.query( [{ kinds: [CALENDAR_RSVP_KIND], authors: [holderHex], '#a': [coord], limit: 5 }], { signal }, ); const ev = [...events].sort((a, b) => b.created_at - a.created_at)[0]; if (!ev) return { status: 'none' }; return { status: ev.tags.find(([n]) => n === 'status')?.[1] ?? 'accepted' }; }, }); const author = useAuthor(parsed?.pubkey ?? ''); const issuerName = author.data?.metadata?.name ?? (parsed ? genUserName(parsed.pubkey) : ''); const data = result.data; const valid = data && data.status === 'valid' ? data : undefined; const event = valid?.event; const venue = valid?.venue; const validThru = event?.tags.find(([n]) => n === 'valid-thru')?.[1]; const expiry = passExpiry(validThru); const accessLevel = event?.tags.find(([n]) => n === 'access-level')?.[1]; const holderPubkey = event?.tags.find(([n]) => n === 'p')?.[1]; const revoked = !!event && event.tags.some(([n, v]) => n === 'status' && v === 'revoked'); const isPark = event?.kind === PARK_ACCESS_CARD_KIND; const isTicket = event?.kind === CALENDAR_EVENT_KIND; const isCrew = event?.kind === SKATE_CREW_KIND; const isRider = event?.kind === RIDER_CARD_KIND; // ── The single verdict a door person needs at a glance ────────────────────── let tone: Tone = 'warn'; let label = t('cardUi.verify.notFound'); let sub = t('cardUi.verify.notFoundDesc'); if (!parsed) { tone = 'bad'; label = t('cardUi.verify.invalidLink'); sub = t('cardUi.verify.invalidLinkDesc'); } else if (data?.status === 'invalid') { tone = 'bad'; label = t('cardUi.verify.invalid'); sub = t('cardUi.verify.invalidDesc'); } else if (data?.status === 'notfound') { tone = 'warn'; label = t('cardUi.verify.notFound'); sub = t('cardUi.verify.notFoundDesc'); } else if (event) { if (isPark && revoked) { tone = 'bad'; label = t('cardUi.v.revoked'); sub = t('cardUi.v.revokedDesc'); } else if (isPark && expiry === 'expired') { tone = 'bad'; label = t('cardUi.v.expired'); sub = t('cardUi.v.expiredDesc'); } else if (isPark && venue?.status === 'mismatch') { tone = 'warn'; label = t('cardUi.v.unverifiedIssuer'); sub = t('cardUi.v.unverifiedIssuerDesc'); } else if (isPark && venue?.status === 'none') { tone = 'warn'; label = t('cardUi.v.notBound'); sub = t('cardUi.v.notBoundDesc'); } else if (isTicket) { // Fail CLOSED: only green when a holder is bound AND their RSVP is positive. // Missing `?h=`, a loading/errored RSVP, or "none" must not read as admitted. const rs = ticket.data?.status; const confirmed = !!holderHex && (rs === 'accepted' || rs === 'going' || rs === 'tentative' || rs === 'interested'); if (confirmed) { tone = 'good'; label = t('cardUi.v.admitted'); sub = t('cardUi.v.admittedDesc'); } else if (!holderHex) { tone = 'warn'; label = t('cardUi.v.noHolder'); sub = t('cardUi.v.noHolderDesc'); } else { tone = 'warn'; label = t('cardUi.v.noRsvp'); sub = t('cardUi.v.noRsvpDesc'); } } else if (isPark) { tone = 'good'; label = t('cardUi.v.valid'); sub = t('cardUi.v.validDesc'); } else { tone = 'good'; label = t('cardUi.verify.authentic'); sub = t('cardUi.verify.authenticDesc'); } } const toneRing = tone === 'good' ? 'border-green-500/40 from-green-500/15 text-green-400' : tone === 'warn' ? 'border-amber-500/40 from-amber-500/15 text-amber-400' : 'border-red-500/40 from-red-500/15 text-red-400'; const Icon = tone === 'good' ? BadgeCheck : tone === 'warn' ? ShieldAlert : ShieldX; // Facts a verifier actually reads. const facts: Array<{ label: string; value: string }> = []; if (isPark) { if (holderPubkey) facts.push({ label: t('cardUi.verify.holder'), value: shortNpub(holderPubkey) }); if (venue?.title || issuerName) facts.push({ label: venue?.kind === CALENDAR_EVENT_KIND ? t('cardUi.verify.organiser') : t('cardUi.verify.venue'), value: venue?.title || issuerName }); if (validThru) facts.push({ label: t('cardUi.verify.validThru'), value: validThru }); if (accessLevel) facts.push({ label: t('cardUi.verify.accessLevel'), value: accessLevel }); } else if (isTicket) { if (holderHex) facts.push({ label: t('cardUi.verify.ticketHolder'), value: shortNpub(holderHex) }); const title = event?.tags.find(([n]) => n === 'title')?.[1]; if (title) facts.push({ label: t('cardUi.verify.kindEvent'), value: title }); } else if (isCrew) { const name = event?.tags.find(([n]) => n === 'name')?.[1]; if (name) facts.push({ label: t('cardUi.verify.kindCrew'), value: name }); } else if (isRider) { facts.push({ label: t('cardUi.verify.issuedBy'), value: issuerName }); } const kindLabel = isPark ? t('cardUi.verify.kindPark') : isCrew ? t('cardUi.verify.kindCrew') : isTicket ? t('cardUi.verify.kindEvent') : t('cardUi.verify.kindRider'); return (
{/* Verdict hero — the whole point of the page */} {parsed && result.isLoading ? (
{t('cardUi.verify.checking')}
) : ( <>
{label}

{sub}

{event && ( <> {/* Facts */} {facts.filter((f) => f.value).length > 0 && (
{facts.filter((f) => f.value).map((f) => (
{f.label}
{f.value}
))}
)} {/* Technical detail (collapsed) */}
{t('cardUi.verify.details')}
{isPark && venue && ( )} {isTicket && ticket.data && ( )}
{parsed && ( isCrew ? ( {t('cardUi.crew.viewCrew')} ) : isTicket ? ( {t('cardUi.event.viewEvent')} ) : ( {t('cardUi.verify.viewCard')} ) )}

{t('cardUi.verify.disclaimer')}

)} )}
); } function Row({ label, value }: { label: string; value: string }) { return (
{label} {value}
); }