109 lines
8 KiB
Markdown
109 lines
8 KiB
Markdown
# SkateSpots — UI/UX Audit & Fix Backlog
|
||
|
||
Findings from a code-level audit across every area (spots/shops, lab, crews, invitations,
|
||
marketplace, stash, wallet, events, tours, calendars, messaging, community stats, profile,
|
||
contacts, near-me, rider card, settings). Each item has an evidence pointer.
|
||
|
||
Work order = top-down, one batch per change-set, shipped through the normal pipeline
|
||
(`npm test` → boot smoke test → auto-deploy).
|
||
|
||
---
|
||
|
||
## P0 — Broken flows & trust (dead controls, false claims)
|
||
|
||
| # | Issue | Evidence |
|
||
|---|-------|----------|
|
||
| 1 | **"Claim This Shop" is a dead button** (no handler) | `SpotDetailPage.tsx:512` |
|
||
| 2 | **Tour "Contact Guide to Book"** links to `/messages?to=` which nothing reads → dead CTA | `TourDetailPage.tsx:55`; route is `/messages/:npub` |
|
||
| 3 | **Contacts "Message"** goes to the generic inbox, not the contact | `ContactsPage.tsx:75` |
|
||
| 4 | **Stash "Share via DM"** has no handler; edit/delete/list are hover-only (unreachable on touch) | `StashPage.tsx:222`, `:207` |
|
||
| 5 | **Marketplace cannot be bought**: no listing detail route; "Pay with Cashu" is a "Coming Soon" stub; every card navigates straight to a DM | `MarketplaceItemCard.tsx:133-152`, `GearSwapItemCard.tsx:165` |
|
||
| 6 | **Map marker opens the Leaflet popup AND the bottom sheet** at once | `SpotMap.tsx:1125`, `MapPage.tsx:1609/1927` |
|
||
| 7 | **Near Me on native: one denial = permanent dead end** (shows *web* browser help) | `MapPage.tsx:1726` + `:723-738` |
|
||
| 8 | **Settings deep links** `?section=follows\|language\|stash` render a blank page with no back | `SettingsPage.tsx:57-70` vs `1194-1215` |
|
||
| 9 | **Events calendar silently inherits an invisible `dateWindow` filter**; Past+Calendar opens the current (empty) month | `EventsPage.tsx:1118`, `:926-929` |
|
||
| 10 | **Crew invite accept says "membership confirmed"** even when only the founder can add the member | `CrewInviteCard.tsx:38-46` |
|
||
| 11 | **Crew "Share link"** promises a one-click invite; recipient only gets a join-request | `CrewInviteDialog.tsx:251-287` |
|
||
| 12 | **Logged-out DM / conversation are dead ends** (no LoginArea), unlike Contacts | `DirectMessagesPage.tsx:171`, `ConversationPage.tsx:152` |
|
||
| 13 | **Message send has no error feedback** | `ConversationPage.tsx:126-137` |
|
||
|
||
## P0 — Errors masquerade as empty / 404
|
||
|
||
| Issue | Evidence |
|
||
|-------|----------|
|
||
| Detail pages map network errors to **NotFound** (implies deleted) | `SpotDetailPage.tsx:240`, `ShopDetailPage.tsx:188`, `CollectionDetailPage.tsx:224`, `CoachDetailPage.tsx:119`, `CrewDetailPage.tsx:245`, `EventDetailPage.tsx:272` |
|
||
| Leaderboards/pages ignore `isError` → "no content yet" | `EventsPage.tsx:814/1220/1265`, `BountyLeaderboardPage.tsx:141`, `ZapLeaderboardPage.tsx:159` |
|
||
| DM inbox error has no retry (Conversation does) | `DirectMessagesPage.tsx:278` |
|
||
|
||
## P1 — Loading / empty-state defects
|
||
|
||
| Issue | Evidence |
|
||
|-------|----------|
|
||
| Spot picker shows false "No spots found" while loading (also in CreateListing) | `SpotPicker.tsx:26,179`; `CreateListingPage.tsx:140-151,967` |
|
||
| Collection skeleton grid ≠ real grid → layout jump on load | `CollectionDetailPage.tsx:54` vs `:314` |
|
||
| Map view has **no empty state** when filters hide everything | `MapPage.tsx:1591-1868` |
|
||
| `EmptyState` nests `<button>` inside `<Link>` (invalid) | `EmptyState.tsx:67-71` |
|
||
|
||
## P1 — Near Me (geolocation) timing & reliability
|
||
|
||
| Issue | Evidence |
|
||
|-------|----------|
|
||
| **Non-denied errors (TIMEOUT/UNAVAILABLE) are swallowed** → promise only fails at the 25s deadline; native path ≈40s worst case | `geolocation.ts:328-343`, `:230-245` |
|
||
| Permission requested **on page load** without a user action | `MapPage.tsx:804-817` |
|
||
| No locate/Near Me control on **desktop** | `MapPage.tsx:1719` (`md:hidden`) |
|
||
| Crews "Near me" shows active state even when no location was obtained | `CrewsPage.tsx:198-205`, `:296` |
|
||
| Location-help overlay has no dialog semantics / Escape / focus trap | `MapPage.tsx:1669-1710` |
|
||
|
||
## P1 — Money correctness
|
||
|
||
| Issue | Evidence |
|
||
|-------|----------|
|
||
| Marketplace sats price uses **hardcoded $1 = 2500 sats** | `MarketplaceItemCard.tsx:119-121,476` |
|
||
| **Wallet page is Cashu-only + "App only" on web** — no balance/send/receive despite the name | `CashuWalletCard.tsx:57-136`, `useCashu.ts:168` |
|
||
| Cashu **backup warning never resolves** (no persisted "backed up" state) | `CashuWalletCard.tsx:702`, `useCashu.ts:100` |
|
||
| ClinkPayButton **silent no-op** without a CLINK wallet; competes with zaps | `ClinkPayButton.tsx:189` |
|
||
| Orphan `/marketplace-listings` page; marketplace missing from primary nav | `AppRouter.tsx:117/158`, `Layout.tsx:75-80` |
|
||
|
||
## P2 — Consistency (systemic)
|
||
|
||
| Issue | Evidence |
|
||
|-------|----------|
|
||
| **No shared page header / container widths** — every area differs (list vs detail) | Lab `max-w-4xl`, Crews `max-w-3xl`, Contacts `max-w-2xl`, DM `max-w-2xl`, Wallet `max-w-lg`, CreateListing `max-w-2xl`, GearSwap/Stash `max-w-7xl`, RiderCard `max-w-2xl`, EventDetail unbounded |
|
||
| EventDetail uses shadcn `primary`/`accent` tokens while the app is neon | `EventDetailPage.tsx:513/548` |
|
||
| RiderCard dead `AwardBadgeDialog` state | `RiderCardPage.tsx:1129,1639` |
|
||
| Lab craft tab grid leaves an orphan row (11 tabs, `grid-cols-5`) | `LabPage.tsx:424-455` |
|
||
| `AboutPage` / `MarketplacePage` use `<a href><Button/></a>` (full reload, nested interactive); About hardcodes stale `v0.1.0` | `AboutPage.tsx:159`, `MarketplacePage.tsx:182` |
|
||
| FAQ page has **no static content** — depends entirely on the AI guide opening | `FAQPage.tsx:42-105` |
|
||
| Profile: sidebar (info/contact) buried below content on mobile; duplicated status indicator | `NIP19Page.tsx:403,709`, `:249/264` |
|
||
|
||
## P2 — Accessibility
|
||
|
||
| Issue | Evidence |
|
||
|-------|----------|
|
||
| Icon-only buttons without `aria-label` (send, refresh, stash actions, settings back, profile actions on mobile) | `ConversationPage.tsx:288`, `CrewChat.tsx:430`, `StashPage.tsx:153`, `SettingsPage.tsx:77`, `NIP19Page.tsx:323` |
|
||
| Form labels not associated (`htmlFor`/`id`) — invite dialog, meeting point, search inputs | `CrewInviteDialog.tsx:190-218`, `CreateTourPage.tsx:1173-1186`, `GearSwapPage.tsx:227`, `StashPage.tsx:843` |
|
||
| Nested interactive elements (`<button>` inside `<Link>`) | `SpotCard.tsx:52`, `EmptyState.tsx:67`, `AboutPage.tsx:159` |
|
||
| Charts have no text alternatives; skeleton uses `Math.random()` heights | `CommunityStatsPage.tsx:316-413`, `:171-179` |
|
||
| Touch targets < 44px across many pages | `CrewInviteCard.tsx:121`, `StashPage.tsx:153`, `EventsPage.tsx:1129`, `SpotCard.tsx:186`, `ContactsPage.tsx:76`, `GearSwapItemCard.tsx:237` |
|
||
| EventsCalendar single-letter weekday headers (`S M T W T F S`) ambiguous | `EventsCalendar.tsx:233` |
|
||
|
||
## P3 — Low / cleanup
|
||
|
||
| Issue | Evidence |
|
||
|-------|----------|
|
||
| Group conversations exist but are unreachable from the inbox (dead hook) | `useDirectMessages.ts:513`, `DirectMessagesPage.tsx:153` |
|
||
| Bounty rank computed **after** `.slice(0,50)`; count shown twice per row | `BountyLeaderboardPage.tsx:156/197`, `:102-110` |
|
||
| Low-contrast informational text (`gray-600/700`) | `StashPage.tsx:260/933/1100`, `WalletPage.tsx:73` |
|
||
| Contacts search matches npub only while UI shows names | `ContactsPage.tsx:167,243` |
|
||
| Shop gallery not zoomable (Spot has ImageLightbox); share feedback inconsistent | `ShopDetailPage.tsx:217-240` |
|
||
|
||
---
|
||
|
||
## Suggested batches
|
||
|
||
- **Batch A — P0 dead flows**: items 1–13 (hide or wire every dead control; fix false crew-invite claim; DM login/error feedback).
|
||
- **Batch B — P0 error states**: replace NotFound-on-error with error+retry across detail pages & leaderboards.
|
||
- **Batch C — Near Me**: geolocation rejection semantics, defer permission, desktop control, native retry, dialog a11y.
|
||
- **Batch D — Money**: live exchange rate, wallet web UX, backup state, Clink fallback, nav/orphan page.
|
||
- **Batch E — Foundations**: `PageHeader` + container widths + touch-target + a11y primitives, then sweep areas.
|
||
- **Batch F — Per-area polish**: loading/empty fixes, calendar, profile mobile, FAQ content, cleanup.
|