Commit graph

216 commits

Author SHA1 Message Date
shakespeare.diy
d577c8e455 Prevent scheduling events in the past + full events-section audit
Audited the whole events flow end-to-end (create, hosting dashboard, discovery
grid, detail, RSVP, sponsors) and confirmed the wiring is coherent:
- Create → parse → display round-trips correctly for sponsors, tour/spot/crew/
  musician links, entry fee, and featured listings
- Sponsor parser safely skips shop-coordinate `sponsor` tags (no name at pos 2),
  so events and tour/shop sponsorships never collide
- Batch RSVP counts feed the grid; hosting dashboard + detail keep per-item counts

Fix surfaced by the audit: CreateEventPage let a host pick a start time in the
past, silently producing a dead event in the Past tab. Added a `min={today}` on
the date pickers and a submit guard (10-min grace) with a clear message.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 15:02:25 +07:00
shakespeare.diy
fbb70983cd Tighten /events header on mobile so cards sit higher above the fold
The filter toolbar was already sticky (stays put while cards scroll under it).
This reclaims the wasted vertical space above it on phones: smaller title,
reduced top padding + margins, and the subtitle is hidden on mobile. Desktop
layout unchanged.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:53:53 +07:00
shakespeare.diy
135fbaab40 Events discovery polish + real bug/perf fixes across the grid
UX (all participants):
- EventCard now shows a live status pill (In 2 days / HAPPENING NOW / ENDED) on
  both the full and compact variants — the at-a-glance signal was previously
  detail-page only
- EventCard shows a "Presented by <sponsor>" ribbon so paid sponsor placements
  get real visibility in the discovery feed (not just after a click)
- Added a bottom legibility gradient so overlays stay readable on any cover

FIX (crash risk): EventsPage getTrustScore did `trustScores?.map.get` — since
useBatchTrustScores returns a plain Map (no `.map`), this threw TypeError once
scores loaded. Now reads the Map directly.

PERF (N+1 query storm): rendering the grid fired one RSVP query PER card
(~100 queries on a full page). Added useBatchEventRsvps — a single #a-filtered
query for all event coordinates — and thread goingCount into EventCard as a prop.
Cards without the prop (detail page, hosting dashboard) keep their own query.

Removed now-dead getRelativeTime/relativeTime/isUpcoming (superseded by the
EventCountdown pill).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:47:02 +07:00
shakespeare.diy
25c57cbb2f Fix event UX correctness: live countdown, cancellation banner, NIP-52 tag cleanup
- FIX: EventCountdown label never updated (memo ignored the interval tick) — now recomputes every 30s for both EventCountdown and useEventStatus
- Add loud cancelled / rescheduled banner on EventDetailPage derived from host updates; close RSVP + dim hero when cancelled so attendees don't show up to a dead jam
- Clean up non-standard NIP-52 `a` tags (['a', coord, '', ''] → ['a', coord]) in RSVP and host-update publishing
- Correct misleading kind comment in useEventRsvps

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:33:28 +07:00
shakespeare.diy
29d09eb6ec Add rich Open Graph / Twitter share metadata + canonical share link to event page
- Per-event og:title/description/image/url + twitter:summary_large_image via useSeoMeta
- Share description composes date + location + blurb; falls back to site card image
- handleShare now uses the canonical /#/event/... URL, richer text, and toast feedback on clipboard copy

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 12:46:45 +07:00
shakespeare.diy
87c81a63c9 Event detail hero redesign + live countdown + sponsors
- Immersive EventDetailPage hero: full-bleed cover with legibility gradient,
  bold uppercase title, type pills, host/music-act chips, and a 3-up meta bar
  (date block · time · where). Past events render dimmed.
- EventCountdown: live status pill — "IN 3 DAYS" / "IN 2H 14M" /
  "HAPPENING NOW" (pulsing) / "ENDED", refreshing every 30s.
- Actions (edit/calendar/share) moved into a clean glass overlay on the cover.
- Event sponsors: EventSponsor type + parse/serialize (mirrors TourSponsor),
  EventSponsors "Presented by" strip (title sponsor hero row + logo grid),
  sponsor editor on CreateEventPage, and sponsor preservation on edit.
- Document event sponsor tag in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 12:04:07 +07:00
shakespeare.diy
78f4a6d09a Host tools: attendee list w/ trust, event updates, edit-alerts, Hosting tab
- AttendeeList: tabbed Going/Interested with faces + names + a "who you trust"
  signal (reuses batch trust scores); replaces the plain avatar stack
- Event Updates (kind 7103): usePostEventUpdate + useEventUpdates; host-only
  composer with update/reschedule/relocation/cancelled categories that p-tags
  every RSVPer so they get notified; public feed on the event page
- EditEventPage: auto-post a reschedule/relocation update to notify attendees
  when the host changes time or location
- Activity bell: new 'event-update' notification type for RSVPers
- EventsPage: "Hosting" tab (logged-in) with a HostingDashboard listing the
  user's upcoming/past events, live RSVP counts, and quick edit/update actions
- Document Event Update kind 7103 in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 11:25:57 +07:00
shakespeare.diy
7439b980bb Events Phase 1: NIP-52 RSVPs, attendee stacks, date filter, host notifications
- Adopt NIP-52 RSVP (kind 31925): parseEventRsvp + RsvpStatus types
- useEventRsvps (counts by status, attendee pubkeys, my status) + useRsvp
  mutation that replaces the user's prior RSVP via a stable d-tag
- RsvpButtons (Going / Interested / Can't make it) + AttendeeStack avatar row
- EventDetailPage: "Who's going" card with RSVP control + attendee avatars
- EventCard: going count on both compact and full cards
- EventsPage: quick date-window chips (Today / Weekend / Week / Month)
- Activity bell: host RSVP notifications (new 'rsvp' activity type)
- Document RSVP schema in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:47:57 +07:00
shakespeare.diy
35915557bb Add in-app routing (foot/bike/moto/car) via OSRM — no external maps handoff
- New src/lib/routing.ts: keyless OSRM client, travel modes, distance/skate-time formatters
- New RouteDialog: self-contained Leaflet map drawing the route from the user's
  location to a spot/shop/event, with a mode toggle and distance/time summary
- Wire the "Directions" buttons on Spot/Shop/Event detail pages to route in-app
  instead of opening Google/Apple Maps

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:17:06 +07:00
shakespeare.diy
5716870863 Redesign create-crew into a guided, idiotproof wizard
Audit finding: the crew form was one 1,100-line endless scroll with ~15
sections in a punishing order (logo dead-last, MLS chat coordinator sitting
inline with the crew name), only one validation gate, and no sense of
progress or what's optional.

Fix — split the flow by intent:
- Create mode: a guided 4-step wizard with a sticky progress bar, per-step
  hints, Back/Continue + "Skip to end", and a sticky footer nav.
    1. The Basics   — logo, name, about, home turf (all you actually need)
    2. Identity & Vibe — vibe tags, crew type
    3. Squad & Spots  — members/roles, home spots, open call
    4. Extras         — treasury, video, sponsor, links, band, NIP-05,
                        agent, and the advanced chat coordinator (reframed
                        in plain language, clearly skippable)
  Only the crew name is gated, and only at step 1. Clickable step dots allow
  jumping back freely.
- Edit mode: unchanged UX intent — renders every section flat on one page
  (power users want it all at once).

All field state and the entire publish/handleSubmit logic are preserved
verbatim; sections were extracted into composable blocks with zero behavior
change. Softened the coordinator copy and the map-pin section so a first-timer
isn't scared off.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:44:59 +07:00
shakespeare.diy
a2c34800ce Add sponsor picker + notifiable crew join-requests
Part A — Sponsor picker:
- CrewSponsorPicker searches the shop directory and emits a 30017 coordinate
  so founders select a sponsoring shop instead of pasting a raw coordinate.
- Wired into CrewForm, replacing the raw coordinate input.

Part B — Notifiable join-requests (kind 31969):
- New addressable join-request event p-tagged to founder + admins, making the
  request→approve direction symmetric with invites (surfaced in the bell, not
  buried in a DM inbox).
- useCrewInvites: useSendJoinRequest, useIncomingJoinRequests,
  useApproveJoinRequest (founder adds member), useDismissJoinRequest (local).
- CrewRequestToJoin now publishes the structured event.
- JoinRequestCard (Add to crew / Dismiss) wired into NotificationBell and
  counted in the unread badge.
- Documented kind 31969 in NIP.md alongside invites.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:23:55 +07:00
shakespeare.diy
50d8d9e614 Wire live Cordn encrypted crew chat over the coordinator (ContextVM)
The crew coordinator is no longer just a pointer — when a crew configures one,
the Chat tab runs through it for real.

- src/lib/cordn: clean transport/crypto split so an MLS (ts-mls) upgrade is a
  drop-in swap of the crypto layer only.
  - types.ts: CordnMessage/CordnEndpoint + envelope kind 4497 + app tag.
  - crypto.ts: sealGroupMessage/openGroupMessage — real NIP-44 group encryption
    (per-recipient sealing), the exact seam MLS encrypt-to-epoch slots into.
  - client.ts: buildEnvelope/publishEnvelope/fetchGroupMessages with per-group
    cursor ordering and ciphertext→author sender binding (anti-spoof).
  - endpoint.ts: resolve a crew's `coordinator` to a usable relay endpoint
    (wss/ws direct; https falls back to the ContextVM relay).
- useCordnChat: fetch-then-poll hook mirroring the NIP-17 chat API.
- CrewChat: uses Cordn when the crew has a coordinator, NIP-17 fallback
  otherwise; header badge shows the active transport (Cordn vs Private).
- CrewCoordinatorCard: reflects that private chat is live.
- NIP.md: document envelope kind 4497, delivery (fetch-then-poll + cursors),
  the nip44-group sealed content, and the honest crypto→MLS seam.

Real encryption, real coordinator routing, non-breaking (fallback intact),
with the MLS ratchet as a clearly-marked next step.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:15:22 +07:00
shakespeare.diy
cbd30fee00 Add skate roles to the crew roster (filmer, photographer, etc.)
A skate team is more than "members" — the filmer, photographer and editor are
core functions. Crews can now assign optional, non-pretentious skate roles:

- Types: CrewRole (filmer/photographer/editor/artist/manager/og) + CREW_ROLE_META
  labels/emoji, `crew-role` tag parser, and `crewRoles` on SkateCrew. Roles are
  additive to membership and grant NO powers (only `admin` does); a member can
  hold roles AND be an admin. Only honoured for founder/listed members.
- CollectiveMembers: optional rolesByPubkey → renders 🎥 Filmer / 📷 Photographer
  flex chips next to each member.
- CrewLeadership: leader + captains show their skate-role chips.
- CrewForm: per-member role toggles + a founder ("Your roles") picker; publishes
  `crew-role` tags scoped to the roster.
- CrewDetailPage: builds rolesByPubkey and threads it to members + leadership.
- NIP.md: document the `crew-role` tag + example.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:07:30 +07:00
shakespeare.diy
91045e17ae Add "The Part" — crew video/montage (YouTube + Instagram)
A skate crew is defined by its footage. Crews can now feature a headline video:
- videoEmbed.ts: safe parser accepting ONLY YouTube and Instagram URLs (full
  parts on YT, clips on IG). Extracts video id / shortcode; rejects everything
  else so we never render an untrusted iframe. No native/self-hosted video.
- CrewVideoSection ("The Part"): cinematic section — YouTube click-to-load
  facade (poster + play button, no heavy iframe/cookies until play) and an
  Instagram permalink embed, framed with skate language.
- SkateCrew gains `videoUrl` (tag `video`), validated through the embed parser.
- CrewForm: "The Part" input with live source detection (YouTube/Instagram) and
  validation; publishes the `video` tag.
- Crews grid: crews with footage flex a "▶ Part" badge as a discovery signal.
- NIP.md: document the `video` tag (YouTube/Instagram only) + example.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:59:50 +07:00
shakespeare.diy
4f3339650d Add crew roles & relationships (Phase 3: sponsor + Cordn coordinator)
- Extend SkateCrew with `sponsorRef` (sponsoring shop, NIP-15 stall coord) and
  `coordinator` (Cordn/MLS encrypted-chat Delivery Service pointer), plus parser.
- CrewSponsorBanner: "Sponsored by [Shop]" banner that resolves and links the
  backing shop; renders nothing if the shop can't be resolved.
- CrewCoordinatorCard: surfaces the crew's private-chat coordinator (E2E · MLS),
  explains what the coordinator can/can't see, shows the operator, and prompts
  the leader to set one up when absent — the UI foundation for Cordn chat.
- Wire the sponsor banner (under hero) and coordinator card (sidebar) into
  CrewDetailPage.
- CrewForm: add "Sponsored by" and "Private Chat Coordinator" inputs with
  validation; publish `sponsor` and `coordinator` tags.
- NIP.md: document the `sponsor` and `coordinator` tags, the structured invite
  kind 36229 (with accept/decline flow), and the Cordn/MLS private-chat model
  and why it's the chosen fit for crews.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:52:33 +07:00
shakespeare.diy
8a384c79d2 Redesign crew detail + crews page (Phase 2: visual)
- New CrewHero: immersive full-bleed banner with per-crew accent glow, club-crest
  logo overlapping the banner, member-count crest, big team name with neon shadow,
  reputation crown + live activity pulse, focus/verification chips, "Led by"
  team-leader inline, vibes and bio. Actions are passed in from the page.
- New CrewLeadership card: frames the founder as "Team Leader" and admins as
  "Captains" with role crests, verified ticks and quick-DM — establishing the
  crew hierarchy and laying groundwork for coordinator roles.
- Rewire CrewDetailPage to use CrewHero + CrewLeadership; wire live activity via
  useCrewsActivity and confirmed-member/admin sets. Removed the old flat hero
  card and now-unused imports.
- Upgrade CrewsPage header into a proper hero band with gradient, glow and
  stat pills.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:07:27 +07:00
shakespeare.diy
e8c1fbc71a Add crew invitation & notification system (Phase 1)
- Upgrade CrewInviteDialog with a "Send invite" tab: invite a specific npub
  with live invitee preview, optional note, and founder-only "invite as admin"
  toggle. Preserves the shareable-link flow on a second tab.
- Add NotificationBell to desktop + mobile headers: unread badge, popover with
  incoming crew invites (accept/decline) and pending-confirmation crews
  (confirm with public/crew-only visibility).
- Wire useSendCrewInvite / useMyCrewInvites / accept / decline hooks and the
  useCrewNotifications aggregator into the UI.
- Membership stays unforgeable (founder p-tag + invitee NIP-32 attestation).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:59:34 +07:00
shakespeare.diy
6ea96171ad Surface Sponsors & Support up top on the profile
- Move sponsors from a buried sidebar card to a prominent full-width "Backed by"
  bar right under the stats strip, above the About/content grid
- Redesigned as a horizontal pill row with an Award badge + gradient/glow accent,
  so reppin' brands reads as a badge of pride
- Removed the duplicate sidebar Sponsors card

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:38:36 +07:00
shakespeare.diy
2bb0f98f45 Redesign profile stats strip into a balanced, scannable grid
- Even 2x2 (mobile) / 1x4 (desktop) grid instead of a ragged flex-wrap where
  "Gear" dropped to its own row and "Member since" floated awkwardly
- Each stat now has an icon badge + prominent count + label, so the number reads
  as a number (previously the tiny colored count looked like a status dot)
- Zero-value stats render in a muted, inert state — a brand-new profile now reads
  as "nothing yet" rather than a wall of dead grey zeros
- Moved "Zaps received" + "Member since" into a clean secondary meta line below,
  keeping the primary grid focused on the four core contributions

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:29:10 +07:00
shakespeare.diy
9958020776 Make profile stat pills clickable — jump to their section
- StatPill now accepts an onClick and renders as a hover-lift button when it has
  a non-zero value (inert div when empty), with clear affordance
- Spots / Shops / Crews / Gear pills smooth-scroll to their matching section
  (added ids + scroll-mt-24 offset so headings aren't hidden under sticky nav)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:23:22 +07:00
shakespeare.diy
deb8fe1995 Profile polish: NIP-05 field UX, share+QR, at-a-glance stats, cleaner empty states
NIP-05 (the field you couldn't find — it existed but was a bare input):
- Promote to its own prominent, self-explaining row (out of the cramped grid)
- Live verification: checks the handle resolves back to your own key, with checking/verified/mismatch/unreachable states, an inline status icon, plain-language copy, and a "How?" help link

Share + QR:
- New ShareProfileDialog: QR of the profile URL (great for IRL/stickers), copy-link, and native share-sheet fallback
- "Share" button added to every profile header

At-a-glance stats:
- Compact stat strip near the top (Spots / Shops / Crews / Gear / Zaps + "Member since")
- Removed the now-redundant sidebar "Contributions" card

Cleaner empty states:
- Spots/Shops sections no longer show dead "nothing here" cards on other people's profiles — hidden entirely when empty
- Own empty Spots state is now an actionable nudge linking to /create-spot

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:08:49 +07:00
shakespeare.diy
a6c90941d2 Ensure Become-Skater ↔ Profile field consistency
Fields collected on the Become-Skater form were being dropped on the profile:
- Instagram: now rendered in the profile Info card (normalizes @handle / full URL, links to instagram.com), matching where website/lightning live
- Location (city/area): now shown as "Based in" in the skate-layer stats row
- hasSkateLayer now also triggers on location/instagram so the skate card renders even when those are the only fields filled

All other fields (photo from kind-0, bio, styles, stance, level/custom level, years, home spot + mapped link, website) already round-trip consistently between the two pages.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:56:28 +07:00
shakespeare.diy
dbae3a1501 Add soft NIP-05 verification for skaters (open directory + "Verified" filter)
Keeps the skater directory fully open — NIP-05 is a trust signal, not a gate:
- Extract reusable NIP-05 verification into lib/nip05.ts (parse, fetch well-known, verify against expected pubkey); refactor useCrewNip05 to use it
- New useSkaterVerifications hook: batch-fetches kind-0 metadata for all skaters and verifies each nip05 resolves to its own pubkey, returning a Set of verified pubkeys
- SkatersPage: add a "Verified (N)" toggle (disabled when none verified) that filters to genuinely verified skaters; show ✓ badge only for truly-verified skaters; verified-aware empty state
- NIP19Page profile: nip05 line now reflects real verification (lime ShieldCheck + "Verified" pill when the handle resolves to the key, muted grey otherwise)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:51:11 +07:00
shakespeare.diy
bb6245e53c Skater profile: browse real map spots for home spot + always use kind-0 profile pic
Home spot:
- Add "Browse spots" button opening a searchable dialog of real mapped spots (useSkateSpots)
- Picking a spot stores its title + an `a` (30818:pubkey:d) coordinate; shows a linked-spot chip with unlink
- Profile page renders a linked home spot as a link to /spot/:pubkey/:identifier
- Parse homeSpotRef from the `a` tag; document in NIP.md

Profile pic consistency:
- Remove the skate-specific image override/upload — the skater card now always
  uses the kind-0 profile picture so both sections show the identical photo
- "Profile Pic" section is read-only with a "Change profile photo" link to settings
- Drop the `image` tag from published skater events; note the change in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:44:12 +07:00
shakespeare.diy
4c9839eb45 Rename "Action shot" to "Profile Pic" and add custom Level option for skaters
- Become-Skater form: photo section relabeled "Profile Pic"
- Level picker now includes a "Custom" toggle with a free-text input (≤40 chars) for labels like "OG", "Weekend Warrior", "Retired"
- Store custom level as a `level_custom` tag (mutually exclusive with preset `level`)
- Render custom level everywhere presets show: profile (NIP19Page), SkatersPage cards, SkaterDirectoryCard
- Document `level_custom` tag in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:34:59 +07:00
shakespeare.diy
3ef11eda61 Refine Lab banner copy, stance to Regular/Goofy, full profile carry-over chips on Become-Skater, remove Back to Leaderboard from profile
- Lab "Just a skater?" banner: drop redundant "→ join the directory" trailer (arrow icon already present)
- Skater stances distinct emojis (🛹 Regular / 🤙 Goofy); already limited to Regular & Goofy
- Become-Skater: fix missing ShieldCheck/Zap imports; show ALL carried-over kind-0 fields (photo, name, bio, website, nip05, zaps) as read-only chips
- Profile (NIP19Page): remove hardcoded "Back to Leaderboard" link and unused ArrowLeft import

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:28:55 +07:00
shakespeare.diy
67d8a24d73 Fuse skater profile (35000) with Nostr identity (kind 0)
Make the skater layer and the universal Nostr profile feel like one person,
with kind-0 as the single source of truth for shared fields.

1. Public profile (NIP19Page): render the skate layer inline — stance, level,
   styles, years, home spot as a branded card right under the About. Own-profile
   nudge ("Add your skate details") when no 35000 exists. Website in the sidebar
   now falls back to the skater profile when kind-0 has none.

2. Become-Skater: prefill bio/website from kind-0 and default the photo to the
   kind-0 avatar (with "using your profile photo" affordance). Store shared
   fields ONLY as overrides — matching values are dropped so kind-0 stays
   authoritative and nothing goes stale. "From your profile" hints + a
   linked-to-your-profile banner make the relationship explicit.

3. Fallback-on-render for website/image/bio wherever the skater field is empty.

4. Bidirectional cross-links: new SkaterDirectoryCard in Settings > Profile
   shows directory status (level + style chips) linking to edit, or nudges the
   user to join; Become-Skater links back to full-profile editing.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:11:36 +07:00
shakespeare.diy
b42c234709 Add Skater profiles (kind 35000) + /skaters directory
- New app-native Skater Profile kind 35000 with `skateskater` marker tag,
  the anti-spam boundary that keeps generic kind-0 Nostr profiles out of the
  directory (queried only from the community relay).
- Skater type, stance/style/level constants, and parser in types.ts.
- useSkaters + useUserSkaterProfile hooks.
- /skaters directory page: immersive hero, live count, search, style filter
  chips, ranked skater grid with NIP-05 verification badges and lazy author
  metadata.
- /become-skater opt-in flow (bio, photo, styles, stance, level, years, home
  spot, location w/ geohash, socials).
- Wire "Skaters" into the mobile nav and a cross-link banner in The Lab so
  plain skaters finally have a home.
- Document kind 35000 in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:58:51 +07:00
shakespeare.diy
9599adaf0d Redesign profile edit form UI/UX + per-line sponsors
- Restructure EditProfileForm into clean, titled sections (Identity,
  Bitcoin & Payments, Skate Profile, Account) with gradient icon
  headers, consistent spacing, and a reusable FieldRow with inline
  icon + validation status.
- Sponsors is now a dynamic per-line list (add/remove rows, Enter to
  add) instead of a textarea; stored newline-separated. The profile
  renders each sponsor as its own badge.
- Lightning address placeholder is now generic (you@yourdomain.com) —
  no specific wallet names anywhere.
- Nicer image upload: click-the-thumbnail-to-upload with hover overlay,
  round avatar preview / wide banner preview.
- Sticky Save bar at the bottom of the form.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:06:32 +07:00
shakespeare.diy
11c67fcf22 Profile: add Lightning, Silent Payment, Sponsors & Cordn fields
Editable in EditProfileForm and displayed on the profile:
- Bitcoin Lightning Address (lud16) — now editable in-form, shown with a
  labeled Lightning row in the Info card.
- Bitcoin Silent Payment Address (BIP-352, custom `bitcoin_silent_payment`)
  — validated (sp1…), shown as a copyable, truncated row.
- Sponsors & Support (custom `sponsors`) — freeform highlight, rendered in
  a dedicated pink "Sponsors & Support" sidebar card.
- Cordn contact link (custom `cordn`) — private MLS messaging link
  (cordn.net), shown as a "Message privately" row in the Info card.

Crews and the CLINK noffer tip button were already present, so they are
left as-is. Added isValidSilentPayment / isValidLightningAddress
validators and documented the custom kind-0 fields in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:00:21 +07:00
shakespeare.diy
b3a49499bb Rename "The Quiver" to "Stash" for brand consistency
Use the established "Stash" term everywhere instead of introducing a new
word. The profile section is now titled "Stash", visibility copy reads
"Shown in your public Stash on your profile", and internal identifiers
were renamed (useUserQuiver → useUserPublicStash, QuiverItem →
PublicStashItem, parseQuiverItem → parsePublicStashItem). NIP.md updated
to match.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 19:41:45 +07:00
shakespeare.diy
32a3cbca88 Public Stash items: "The Quiver" showcase + marketplace hybrid
Previously the "Public" visibility on Stash items did nothing — the
whole Stash is a single NIP-44 encrypted blob, so nobody could ever see
a "public" item. This makes Public actually mean public.

Model (hybrid A + marketplace):
- Public items are now ALSO published as separate unencrypted NIP-99
  kind 30402 events tagged `t: stash-showcase`, keyed by `stash-<id>`.
- Public + not-for-sale → showcase-only (`showcase: true`, price 0):
  shows on the profile "Quiver", filtered OUT of the sellable market.
- Public + for sale/trade → full listing: shows on the profile Quiver
  (badged For Sale/Trade) AND flows into the marketplace.
- Going private or deleting an item publishes a NIP-09 deletion for its
  public event, keeping everything consistent.

Changes:
- New hook useStashShowcase (sync/unpublish + useUserQuiver query).
- StashPage wires add/edit/remove through sync handlers; honest
  visibility copy in add + edit dialogs.
- Profile page (NIP19Page) renders a new "The Quiver" grid with
  For Sale/Trade badges + prices, and a Gear count in Contributions.
- Marketplace hooks skip showcase-only events so they don't clutter
  buy/sell listings.
- NIP.md documents the showcase event schema.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 19:33:34 +07:00
shakespeare.diy
9086123240 Fix messy Stash header/toolbar layout on mobile
- Let the title badges (Encrypted · Syncs everywhere · sats) wrap
  gracefully instead of colliding with the toolbar controls
- Hide the list/grid view toggle on the empty state (nothing to toggle)
  so it no longer clutters the space next to the Add button
- Give the title row room to grow on mobile (min-h-14 + padding)
  instead of forcing everything into a fixed 56px row
- Make the search input flex-fill and shrink the category select on
  small screens so the filter bar no longer overflows
- Add aria-labels to the view-toggle buttons

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 19:17:40 +07:00
shakespeare.diy
955d5902d5 Add RK69 DIY Skatepark to Supporters / Friends
Links to https://www.instagram.com/rk69diyskatepark, placed next to SNT4EVER.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-31 19:42:16 +07:00
shakespeare.diy
8ae852f1b8 Add Judges tab to The Lab
- Flesh out SkateJudge as a full craft-based Lab profile (parseCraftBase),
  with JUDGE_FORMAT / SPECIALTY / LEVEL label sets covering Game of S.K.A.T.E.,
  best trick, jams, contests, am series, world tour stops, etc.
- Wire judge into useLabProfiles (union type, self-tag, parse case)
- Add "judging" ServiceType to LabPage: tab, accent, filter sections, empty
  state, counts, profile card icon, and ?craft=judging deep link
- New BecomeJudgePage + /become-judge route + FAB entry
- CoachDetailPage renders judge profiles (accent, icon, labels, noun, title)
- Community Stats "Judges" card now links to /lab?craft=judging
- Document kind 35012 (skatejudge) in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:30:43 +07:00
shakespeare.diy
9cefa29640 Make community stat cards clickable to view full lists
- Wire `to` links on stat cards: spots/shops/tours → map, events → /events,
  mappers → /bounties leaderboard, Lab crafts → /lab?craft=<type>, crews → /crews
- Stats already read from the canonical goskatespots relay and the map limit
  matches (2000) so numbers stay consistent with what's on the map

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:17:45 +07:00
shakespeare.diy
94a88b2ba0 v1.2.1: Community stats now show ALL Lab crafts + count every craft type
- useGrowthStats now queries and exposes every Lab craft individually: artists,
  graffiti, tattoo, trainers, musicians and judges (previously folded into an
  opaque "other" bucket with no visible breakdown).
- CommunityStatsPage renders a card for all 11 Lab crafts, so the section matches
  what /lab actually lists instead of only showing 5 categories.
- labTotal + the content-mix donut's "Lab" slice now sum every craft, keeping the
  "total" badge and chart consistent.

Note on relay mismatch: the app dedupes addressable events (keeps newest per
pubkey+kind+d) and merges across the community relay + the user's relays, so
counts can legitimately differ from a raw single-relay browser that shows stale
duplicate versions.

Bump version to 1.2.1 (versionCode 10201).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:07:13 +07:00
shakespeare.diy
677ccb8a82 v1.2.0: Spedn label, multi-relay community stats, GrapheneOS Near-Me fix
1. Map filter: renamed the "Bitcoin" toggle to "Spedn" (matches the toolbar
   button); tooltip now says "Spedn your sats".

2. Community stats (/stats): useGrowthStats now queries the same relay set as the
   rest of the app (community relay + user relays) via useSkateRelays instead of
   only the single hardcoded SKATE_RELAY. Lab profiles / spots / etc. that live on
   a user's own relay are now counted, so the numbers match what /lab shows.

3. Near Me on GrapheneOS: even after granting the OS location permission, the
   WebView's navigator.geolocation stays blocked → "permission denied". Fixed by
   fetching the position from the native Capacitor Geolocation plugin first
   (LocationManager, honours the granted permission), falling back to
   navigator.geolocation only if that fails. Also auto-inject
   ACCESS_FINE/COARSE_LOCATION into AndroidManifest via the manifest script.

Bump version to 1.2.0 (versionCode 10200).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:54:00 +07:00
shakespeare.diy
4ed1d9a803 Fix crew card layout, Amber web login hang, Stash encryption gate, and add signup welcome step
- CrewCard: rebuilt to fit any screen width — clip overflow, truncate name/location, single wrapping badge row, consistent avatar sizing; responsive header
- Amber web login: after opening Amber, drop user on manual paste step after 2.5s so the spinner never hangs when the callback opens in a new tab or clipboard is blocked; keep polling on the paste step so a late callback still auto-completes
- Stash: hasNip44/hasNip04 now fall back to the active login when pubkey matching fails, so logged-in nsec/amber users are no longer wrongly told "Encryption Required"
- Signup: added an explicit welcome step that explains account creation and only generates the keypair when the user taps "Create my account" (no more silent auto-creation on dialog open)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 13:27:24 +07:00
shakespeare.diy
a83611072a Fix Amber NIP-44 gate on Stash, mobile Crew detail layout, remove Quick Snap
- signerCapabilities: recognize x-amber login type as NIP-44/NIP-04
  capable (Amber implements nip44_encrypt/decrypt via native intents and
  URL redirects). Fixes "Encryption Required" wall on Stash for users
  logged in with Amber on Android.
- CrewDetailPage: restructure hero header so actions wrap below the
  title on mobile instead of squeezing it in a cramped 3-column flex;
  smaller avatar/title on small screens, truncating location.
- MapPage: remove Quick Snap from the mobile Add speed-dial and all its
  handlers, refs, web-fallback input, and now-unused imports.
- zapstore.yaml: explicit orange icon + metadata_sources: [] so the
  correct logo is always used on the Zapstore listing.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 12:31:34 +07:00
shakespeare.diy
33cfe692ed Add privacy transparency: dedicated /privacy page, onboarding privacy note, Sasquatch VPN/Tor Q&A, footer link
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:46:47 +07:00
shakespeare.diy
3fd82f2425 #2 Reduce console noise: strip debug logs from relay queries, publish, trust score, weather, coaches, NostrSync
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:17:01 +07:00
shakespeare.diy
b5b9faeec8 #1 Onboarding polish: add 'Why bother?' skater value-prop section, fix dead /radio link (-> Crews), soften Nostr FAQ overclaim
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:02:03 +07:00
shakespeare.diy
49325be38e #5 Reframe 'permanent/can't delete' copy around user data ownership & relay choice (onboarding, relay education, about)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 09:59:43 +07:00
shakespeare.diy
8a6d84ca3d Fix map tiles (drop keyed CARTO for free OSM + CSS dark filter), restore corrupted parseSkateCoach in types.ts
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 09:56:47 +07:00
shakespeare.diy
1c4987c8bf Add desktop-only "Get on Zapstore" CTA to map Welcome Banner
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-20 19:41:53 +07:00
shakespeare.diy
13934c0e74 Fix newsletter admin gating: use isAdminPubkey from lib/admins instead of undefined NEWSLETTER_ADMIN_PUBKEYS
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-20 16:16:09 +07:00
shakespeare.diy
82a74e01e0 Extend admin takedown to Lab profiles and gear/marketplace listings
Completes the admin moderation coverage from the previous commit.

Lab profiles (coaches, filmers, editors, photographers, builders, artists,
graffiti, tattoo, trainers, musicians):
- useLabProfiles / useLabProfile and useCoaches / useCoach now filter out
  admin-hidden entries (list) and 404 hidden ones (detail) via the shared
  moderation layer. Added filterAdminHiddenMixed() for the mixed-kind Lab
  union (kind read from each item's event).
- CoachDetailPage (the shared Lab detail page) now shows the delete/takedown
  card to admins too, not just the owner, with admin-specific copy.
  DeleteEntityDialog already handles the owner-or-admin logic.

Gear / marketplace listings (NIP-99, kind 30402):
- useMarketplaceItems / useMarketplaceAtSpot filter out admin-hidden listings.
- GearSwapItemCard: admins get a "Remove (admin)" action in the card menu with
  a confirmation dialog that routes through useDeleteEntity (NIP-09 delete +
  hidden label). Report stays for non-owners.

SECURITY.md: updated the admin-moderation coverage list and auditor file map.
Build + lint pass, no console errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 19:17:16 +07:00
shakespeare.diy
13eece3a1e Admins can delete/take down any listing (spot, crew, tour, event, shop)
The two SkateSpots admin pubkeys (already in src/lib/admins.ts) can now remove
any listing, not just their own. Because a NIP-09 deletion signed by a
non-author isn't honored by most relays, admin takedowns are made reliable with
a client-side moderation layer:

- useDeleteEntity: owners still publish a plain NIP-09 kind:5. Admins acting on
  someone else's content publish the kind:5 AND a NIP-32 kind:1985 `hidden`
  label referencing the target coordinate/id, authored by the admin.
- useAdminModeration (new): fetches admin-authored `hidden` labels app-wide and
  exposes a fast lookup + filterAdminHidden() helper. Only labels whose author
  is in the hardcoded ADMIN_PUBKEYS are honored (author = trust boundary,
  re-verified client-side; never derived from a d-tag).
- Wired the hide filter into the list + detail hooks for spots, crews (incl.
  by-focus), tours, events, and shops via React Query `select`, so removed
  content disappears from lists and 404s on its detail page.

UI:
- DeleteEntityDialog now renders for owner OR admin, with distinct
  "Remove (admin)" framing and copy (used on Crew, Coach, Tour, Event pages).
- SpotDetailPage & ShopDetailPage: routed their inline delete through
  useDeleteEntity, show the delete button for admins, and relabel the confirm
  dialog for admin takedowns.
- Added the delete control to Tour and Event detail pages (previously none).

SECURITY.md: documented the admin moderation trust boundary and extended the
auditor file map. Verified build + no console errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 17:51:34 +07:00
shakespeare.diy
b4b2375921 Security: fix stored XSS in map popups + add web CSP
Critical: Leaflet marker popups built untrusted Nostr event fields (and
BTC Map API fields) into innerHTML, bypassing React's output encoding. Any
user could publish a spot/tour/crew/event with an <img onerror> payload in a
text field; it executed on /map render with no interaction, letting injected
script read the plaintext nsec from localStorage (nostr:login) and the Cashu
seed (cashu-mnemonic) — permanent, unrotatable account + wallet takeover.

Fixes in src/components/SpotMap.tsx:
- Rebuild all popup DOM with createElement + textContent instead of innerHTML
  for every untrusted field: spot.location; tour.title/location/meetingPoint;
  evt.title/location; crew.name/location; BTC Map name/address/opening_hours/
  phone/description.
- Validate every event/API URL with sanitizeUrl() before it reaches an
  href/src: crew.image (popup + marker icon) and BTC Map website. Drop
  anything that isn't http(s). tel: links reduced to safe characters.
- Replace inline onerror handler on the crew marker image (would be blocked
  by CSP anyway) with a scripted img.onerror fallback.
- Add popupRow() helper so untrusted detail rows can never be innerHTML'd.

Defense in depth (web only, native Capacitor build unaffected):
- Add public/_headers with a strict Content-Security-Policy: default-src
  'none'; script-src 'self' 'wasm-unsafe-eval' (no unsafe-inline/eval);
  object-src 'none'; base-uri 'none'; frame-ancestors 'none'; plus the
  https:/wss: allowances a Nostr client genuinely needs for relays, media,
  and mints. Also X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
  Permissions-Policy.
- Move the inline boot-crash-guard script to public/boot-guard.js so
  script-src can stay 'self' with no 'unsafe-inline'; index.html now loads it
  externally. The guard also now paints errors via textContent, not innerHTML.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-16 22:12:26 +07:00