Commit graph

20 commits

Author SHA1 Message Date
SkateSpots
6def78fffe chore(lint): clear all no-unused-vars warnings (position-precise)
Removed/renamed the remaining 39 unused locals/dead code, file-by-file:
- unused destructured bindings/params (remove the binding, keep the call)
- unused single-line pure consts and dead functions/interfaces removed
- deleted the dead _FeedItem component and its now-unused imports
- amber _readChunks self-call fixed; SpotMap popup call site updated

Warnings: 182 -> 7 (only react-refresh/only-export-components remain).
no-explicit-any=0, exhaustive-deps=0, no-unused-vars=0.
Gate: 101 tests, 0 errors, boot smoke green. AGENTS.md baseline + guidance updated.
2026-09-26 22:20:21 +07:00
SkateSpots
721a5d4b67 chore(lint): clear all react-hooks/exhaustive-deps warnings
Triage per file (not a blanket sweep):
- Memoise shared getters with useCallback and depend on them:
  EventsPage/LabPage/MapPage getTrustScore+getCommentCount (+MapPage getZapTotal)
- Memoise derived values that were recreated each render: CrewChat 'messages',
  RiderCardPage 'cardData'
- Justified eslint-disable for intentional mount-only effects with a comment:
  AppProvider (seed defaults/theme), SpotMap (Leaflet init once),
  SpamFeePayment (auto-pay keyed to invoice/state, not the recreated handler)
- EventCountdown: keep the intentional 'tick' recompute dep (documented disable)

exhaustive-deps: 14 -> 0. Warnings 60 -> 46 (39 unused-vars, 7 react-refresh).
Gate: 101 tests, 0 errors, boot smoke green. AGENTS.md baseline updated.
2026-09-26 22:09:43 +07:00
SkateSpots
d1f8870379 chore(lint): remove all explicit any + sweep dead imports
- Fix all 12 no-explicit-any (NPool, NostrEvent[], LabProfile, structural
  Cluster type, explicit SkateSpot cast) -> 0 remaining
- Remove unused imports/vars across 48 files (manual + a verified codemod for
  unused import specifiers only); tsc + tests + boot smoke all green
- Warning backlog: 182 -> 76 (remaining: ~55 no-unused-vars locals,
  14 react-hooks/exhaustive-deps, 7 react-refresh)
- AGENTS.md baseline updated
2026-09-26 21:40:42 +07:00
SkateSpots
cbd12f3e52 fix(ux): Batch A part 2 — stash, crew invites, map marker
- Stash: remove the handler-less 'Share via DM' button; action overlay stays
  visible on touch (was hover-only, so edit/delete were unreachable on mobile)
- Crew invite accept no longer claims membership is confirmed (only the founder
  ratifies the roster) — both the toast and the accepted state now say so
- Crew invite share message no longer promises a one-click join
- Map marker: don't bind the Leaflet popup when a host supplies onSpotClick
  (MapPage's bottom sheet) — both used to appear at once
2026-09-26 17:47:38 +07:00
shakespeare.diy
358708ee27 perf: shrink map marker payload and extend vendor chunking
C (marker): Reuse the existing ~24 KB logo.png for the "you are here"
map marker instead of the 2.9 MB user-location.png (byte-identical copy
of logo-sticker). Removed the unused 2.9 MB PNG from public/images,
cutting deploy/APK size. Same artwork, ~99% smaller payload.

B (chunking): Extend the existing manualChunks in vite.config.ts to
split the largest page-specific libraries into their own vendor chunks
(hls.js, html5-qrcode, recharts) so they load only on the routes that
use them. Capacitor single-bundle branch and React core grouping left
untouched.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-24 19:18:14 +07:00
shakespeare.diy
a40c86e655 Map pins: use a single generic 🛹 glyph instead of per-category emojis
Replaced the per-category emoji map (street/park/DIY/plaza/transition) with one
generic skateboard glyph on every spot pin, per request. The pin's crisp shape,
tight halo and shelter roof-cap (covered/indoor) are unchanged. Dropped the now-
unused `category` option from createSkateSpotIcon and its call sites.

Build clean; the earlier "CATEGORY_GLYPH is not defined" console errors were
from a stale intermediate bundle and do not occur in the current build.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-10 18:50:34 +07:00
shakespeare.diy
345af97788 Map Pass 1: crisp pins, spot-type & shelter glyphs, floating loading, micro-interactions
Visual foundation for the map (ideas #2, #3, #4, #6 — features #5/#7 intentionally
deferred to a later pass):

#2 Crisp pins, not blobs:
- Reworked createSkateSpotIcon — solid neon teardrop with a TIGHT glow halo
  (inset dark ring + focused shadow) instead of the soft box-shadow that read as
  a fuzzy cloud on light tiles. Cluster badges given the same crisp treatment.

#3 Marker = spot type (a readable language):
- Category emoji inside each pin: 🛹 street / 🏞️ park / 🔧 DIY / 🏛️ plaza / 🌊 transition.
- Covered/indoor spots get a cyan roof "cap" so dry spots stand out at a glance —
  the payoff of the shelter data living on the map, not just a badge.
- createSkateSpotIcon now takes an options object (weatherColor/weatherIcon/
  category/shelter); all call sites updated. Weather-layer colouring unchanged.

#4 Less scrim, floating loading:
- Suspense map-load fallback dimming reduced (bg-black/50 → /20).
- "Finding spots" pill moved from dead-centre (top 46%) to a slim top pill so it
  never blocks the map where the spots are.

#6 Micro-interactions (CSS, GPU-friendly, reduced-motion aware):
- New pins drop in with a spring; hover/focus/tap lifts the pin and pulses its
  neon halo once.

Build clean, no console errors. No tile/theme change — the "og" light map is
untouched per request.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-10 18:46:15 +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
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
538ef67b0b Security: remove hardcoded OpenRouter key (C1) + escape weather HTML (H1)
Address the two genuinely exploitable findings from the OpenCode audit:

C1 (critical) — sasquatchBrain.ts shipped a hardcoded OpenRouter API key in
the client bundle, extractable and abusable by anyone. Removed it; OpenRouter
is now opt-in via VITE_OPENROUTER_API_KEY and skipped when unset. Sasquatch
still works for everyone via the existing keyless Pollinations fallback.
(The leaked key must also be rotated/revoked on openrouter.ai.)

H1 (high) — SpotMap popup interpolated external weather-API data directly into
innerHTML. Added an escapeHtml() helper and applied it to every weather/AQI/
forecast value so a compromised weather API can't inject HTML/JS.

Triage of the rest: C3 (crew agent derived key) is a documented, deliberate
tradeoff — agent holds no funds and only speaks for itself. H2 (chart.tsx
dangerouslySetInnerHTML) is the stock shadcn chart with app-controlled config,
not user input. H3/H4 (GitHub Actions) don't apply — no .github/ in this repo.
C2 (NWC in localStorage) is standard for web Nostr clients. npm audit fix +
key rotation are operator steps done outside the app.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 17:04:53 +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
shakespeare.diy
58aaeb7b16 Map: add "you are here" user-location marker (SkateSpots logo + pulse)
After Near Me centers the map, there was no indicator of where the user
actually is. Add a Google-Maps-style location marker, brand-flavored:

- Circular badge using the SkateSpots logo (public/images/user-location.png)
  with a red (#ff0040) ring, sitting at the user's exact coordinates.
- Pulsing translucent accuracy halo (ssPulse keyframes) so it reads as a
  live "you are here" and doubles as subtle motion.
- Rendered above all other markers (zIndexOffset), with a "You are here"
  popup. Created/moved/removed reactively from a new `userLocation` prop.

Wired MapPage's userLocation state through to SpotMap (lat/lon → lat/lng).
Marker image path uses import.meta.env.BASE_URL so it resolves on both
web ('/') and the Capacitor APK ('./').

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 00:55:52 +07:00
shakespeare.diy
f10aa7971c Step 3: Crews — map-anchored collective feature (kind 35006)
- New SkateCrew kind 35006 + type/parser + useCrews/useCrew hooks
- /crews directory, /create-crew, /edit-crew, /crew/:pubkey/:identifier pages
- Reuses the collective engine: NIP-32 membership attestation (confirmed/pending),
  CollectiveMembers + ConfirmMembershipCard, ClinkPayButton, SpotPicker
- Activity v1: home spots + crew member feed (no events, per plan)
- Map: teal/cyan Crews layer at home turf with crew markers + popups;
  wired through SpotMap props, layer toggles (mobile popover + filters sheet),
  and mobileLayerCount badge
- Nav: Crews added to navItemKeys (desktop header, mobile bottom bar, sidebar)
  with en/es i18n keys
- NIP.md: documents kind 35006, tag taxonomy, and shared membership mechanism

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:59:50 +07:00
shakespeare.diy
88a36e52ae Add Events as an opt-in map layer
Events can now be discovered directly on the map:
- New "Events" layer (lime #ccff00, calendar pin marker) plotting upcoming,
  geo-located events. Off by default — users opt in via the Layers control.
- Event markers show a popup with title, date/time, location, and a
  "View Event" button linking to the event detail page.
- Only future/ongoing events with a valid geohash are plotted (past events
  are filtered out to keep the map clean).
- Added to the mobile Layers popover, desktop Layers popover, and the mobile
  filter drawer's "Show on Map" grid. Layer count badge now tracks 5 layers.
- SpotMap gains `events` + `showEvents` props and a createEventIcon marker.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 10:48:24 +07:00
shakespeare.diy
889eb922eb map: move attribution to top-right below Near Me, add Vibed with Shakespeare below Quick Snap
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 20:01:28 +07:00
shakespeare.diy
f8b14f1056 map: switch tile layer to CartoDB Voyager
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 19:59:11 +07:00
shakespeare.diy
682e4d802b map: dark tiles, near me button, bottom sheet, floating filter pills
- Swap OSM tiles for CartoDB Dark Matter (near-black bg, neon markers pop)
- Add floating Near Me button (top-right, crosshair icon, flies to user location)
- Add SpotBottomSheet: tap a marker → drawer slides up from bottom with
  spot thumbnail, title, location, type badges, author, View Details + Directions CTAs
  (map stays fully visible behind the sheet)
- Add flyTo prop to SpotMap for programmatic map navigation
- Add floating layer filter pills (spots/shops/gear/tours) on map surface
  bottom-left, always visible, no toolbar required

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 19:57:06 +07:00
shakespeare.diy
9da1796774 fix: cap map zoom out, prevent grey bands and tile duplication
- minZoom: 2 — stops user zooming past a single world view
- maxBounds: full lat range with infinite lng — locks vertical pan to the world
- maxBoundsViscosity: 1.0 — hard clamp, no elastic bounce into grey space
- TileLayer minZoom: 2 to match

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 16:21:58 +07:00
shakespeare.diy
f19851da92 security: implement all 8 protection measures
## 1. sanitizeUrl() (already existed, verified applied)
- Applied to ShopDetailPage, CoachDetailPage with urlDomain() tooltip hints
- Blocks javascript:, data:, vbscript: and all non-http(s) schemes
- Returns null for invalid URLs — link simply doesn't render

## 2. Geohash validation in parsers
- Added isValidGeohash() in types.ts using ngeohash.decode()
- Min 4 chars (country-level precision rejected), max 12 chars
- Rejects lat/lng outside Earth bounds
- Rejects 0,0 "null island" — classic bot artifact
- VPN/Tor unaffected: geohash comes from map pin, not user IP

## 3. Content length limits in all parsers
- MAX_TITLE_LEN=100, MAX_DESCRIPTION_LEN=3000, MAX_LOCATION_LEN=200
- MIN_TITLE_LEN=2, MIN_GEOHASH_LEN=4
- Applied to parseSkateSpot, parseSkateShop, parseSkateCoach,
  parseSkateFilmer, parseSkateEditor, parseSkateEvent

## 4. Smart WoT default (already in codebase, verified correct)
- Logged out / <5 follows → 'all' (don't punish new/anon users)
- Logged in with ≥5 follows → 'trusted' (sort trusted first)
- Soft mode: trusted content floats to top, nothing hidden
- Active in MapPage and EventsPage

## 5. Trusted count stats in useGrowthStats
- Added trustedSpots and trustedMappers to GrowthStats type
- Heuristic: pubkeys with ≥2 published items = organic community member
- CommunityStatsPage now shows "🛡 X from active accounts" below spot count
- Purely additive — both numbers always visible

## 6. OfficialBadge component (already existed, wired up)
- Added to SpotCard, EventCard, ShopDetailPage, CoachDetailPage
- Only renders for FOUNDER_PUBKEY (20e7c953...)
- Zero-friction: invisible for all other pubkeys
- Impersonation attempts immediately visible

## 7. Leaflet marker clustering
- Installed leaflet.markercluster v1.5.3 + @types
- MarkerClusterGroup replaces direct marker.addTo(map)
- Custom cluster icon: red/orange gradient circle with count
- maxClusterRadius: 60px, spiderfyOnMaxZoom: true
- MarkerCluster CSS loaded from CDN in index.html

## 8. DM phishing warning banner
- PhishingWarningBanner component in DirectMessagesPage
- One-time dismissible (localStorage key: dm-phishing-warning-dismissed)
- Message: SkateSpots will never DM you asking for keys/payments
- Uses useLocalStorage hook, Shield icon, X dismiss button

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:03:15 +07:00
shakespeare.diy
e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00
Renamed from src/src/components/SpotMap.tsx (Browse further)