Commit graph

23 commits

Author SHA1 Message Date
SkateSpots
07e844e0fb fix: declare @nostrify/types so nostrify type exports resolve (tsc gate) 2026-09-26 15:10:11 +07:00
SkateSpots
c4586292d8 fix: declare websocket-ts + @std/encoding so @nostrify/nostrify deps resolve 2026-09-26 14:38:27 +07:00
shakespeare.diy
f25fc2bd74 fix: resync package-lock with package.json + robust vitest setup path
- Regenerate lockfile so @tanstack/react-query (and the related
  query-core / persist-client packages) resolve to 5.102.8, matching
  package.json. The committed lockfile was stale at 5.75.1, which made
  `npm ci` abort with "lock file's @tanstack/react-query@5.75.1 does not
  satisfy 5.102.8" and left vitest running against a half-broken
  node_modules.
- Use path.resolve(__dirname, './src/test/setup.ts') for vitest
  setupFiles so the setup module is located relative to the config file
  rather than the invocation cwd, fixing the "Cannot find module
  .../src/test/setup.ts" resolution failure.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-24 20:08:44 +07:00
shakespeare.diy
8d464a05f3 fix: resolve pre-existing tsc errors blocking the test gate
Two type errors (surfaced by `npm run test` / tsc --noEmit on a full
toolchain; the in-browser bundler does not run tsc, so they were latent):

- useCVMClient.ts: remove a malformed cast
  `spots as typeof spots & { tags; created_at }[]` feeding
  deduplicateByIdentity(). The intersection dropped pubkey/kind and broke
  the reassignment to `spots`. deduplicateAddressable() already returns
  the correct shape, so the cast was both wrong and unnecessary.

- queryPersister.ts: import PersistedClient / Persister from
  @tanstack/query-persist-client-core (their canonical source) instead of
  @tanstack/react-query-persist-client, whose `export *` re-export tsc
  won't resolve for named type imports. Added the core package (5.102.8,
  matching the pinned wrapper) as an explicit dependency.

Both are pre-existing issues on the branch, unrelated to the audit
remediation commits.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-24 19:46:27 +07:00
shakespeare.diy
f316c77806 Security hardening (audit F1-F4): secure Cashu seed in native Keychain/KeyStore with mandatory first-time backup; verify event signatures for trust scores (WoT + NIP-85 Relatr); add safeJson guard on config boot parse; delete dead CashuWallet.tsx with raw alert(mnemonic)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-24 18:13:58 +07:00
shakespeare.diy
a984084464 Instant cold-start: query persistence, optimistic geolocation, skeleton pins
Three UX wins so the app feels "already loaded" the moment it opens:

1. IndexedDB React Query persistence (lib/queryPersister.ts)
   - PersistQueryClientProvider rehydrates the last-seen spots, shops,
     events, tours, crews, coaches, listings and author profiles from
     IndexedDB on cold start, so returning users land on a populated map
     instead of a blank one; fresh data refetches quietly in the
     background.
   - Strict allow-list / deny-list: only public, JSON-serializable,
     cold-start-relevant queries are persisted. Private/encrypted data
     (stash, DMs, contacts, wallet/nwc/cashu/bitcoin, signer) is NEVER
     written to disk. 24h maxAge + version buster keep it fresh & safe.
   - Pinned @tanstack/react-query + query-core + persist-client to a
     single 5.102.8 to avoid a query-core mismatch (partialMatchKey).

2. Optimistic geolocation (lib/lastLocation.ts)
   - getCurrentPosition now caches every successful fix.
   - The map opens ALREADY centered on the user's last-known area
     (city zoom) instead of the world view; "you are here" + proximity
     sort seed from the cache instantly.
   - "Near Me" flies to the cached location immediately on tap, then
     refines to the fresh GPS fix. Silent background warm-up on web map
     mount so a later tap resolves instantly.

3. Cold-start skeleton (components/MapSkeletonPins.tsx)
   - When the map is genuinely empty + loading, show elegant pulsing
     ghost pins + a "Finding spots" pill instead of a blank map; they
     vanish the instant real (or cached) spots render. Splash now
     signals content-ready when spots actually resolve.

Respects prefers-reduced-motion throughout. Build + runtime verified
clean (no console errors).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-09 21:58:22 +07:00
shakespeare.diy
a3e04de1c7 Restore caret ranges for @nostrify packages
Pinning exact versions produced a broken @nostrify/nostrify variant on
some installs (raw .ts source missing @std/encoding / websocket-ts deps).
Revert to caret ranges so npm resolves the complete, working tree.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 23:10:54 +07:00
shakespeare.diy
144227b88b Fix zap request construction for newer nostr-tools types
The clean node_modules reinstall pulled newer @nostrify/* and nostr-tools
minors whose nip57.makeZapRequest signature changed (event param became a
required full NostrEvent), breaking tsc in useZaps and usePurchasePro.

Build the kind:9734 zap request events directly per NIP-57 instead of
relying on nip57.makeZapRequest, so we're decoupled from that helper's
changing type signature. Preserves correct target tagging:
- addressable events → 'a' tag (kind:pubkey:dTag)
- other events       → 'e' tag (event id)
- profile-only (pro purchase) → no event tag, keeps pro-tier/pro-expiry.

Pin @nostrify/nostrify, @nostrify/react and nostr-tools so the tree
stays reproducible.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 22:14:46 +07:00
shakespeare.diy
6dc7de9e5c Add @capacitor/cli and @capacitor/android for native Android builds
Fixes 'npx cap: could not determine executable to run' and the Gradle
'No variants exist for :capacitor-android' error by tracking the Capacitor
build tooling in package.json so it survives npm install.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:23:32 +07:00
shakespeare.diy
b3a025ca77 Native geolocation for Near Me + desktop map fits viewport with footer (v1.0.73)
1. Near Me "location blocked" on the APK:
   - navigator.geolocation returns PERMISSION_DENIED inside the Android WebView
     because the WebView never gets the OS location permission, so the app
     showed a bogus "your browser blocked location" help sheet.
   - New src/lib/geolocation.ts: platform-aware getCurrentPosition(). On native
     it uses Capacitor's Geolocation plugin via registerPlugin('Geolocation')
     from bundled @capacitor/core (same runtime pattern as the camera/Amber
     fixes) and calls requestPermissions() to trigger the real Android prompt;
     on web it falls back to navigator.geolocation.
   - MapPage handleNearMe, proximity sort, and quick-snap location now use it.
     On native, a denied permission shows an OS-permission toast instead of the
     browser help sheet.
   - Added @capacitor/geolocation dependency (native side picked up via cap sync;
     no bare import in the JS bundle).

2. Desktop map didn't fit the viewport (had to scroll to see footer):
   - Map wrapper was md:h-[calc(100vh-4rem)] — full height minus header only,
     leaving no room for the footer.
   - Added --footer-desktop token (5rem) and changed the desktop height to
     calc(100vh-4rem-var(--footer-desktop)) so map + footer fit without scroll.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 20:28:06 +07:00
shakespeare.diy
fc7564a6f8 Platform-aware wallet mints (Option C: native + browser scoping)
- Add @capacitor/core + src/lib/platform.ts (isNativePlatform/getPlatform, web-safe)
- mintReachability: on native (no CORS jail) short-circuit probe to 'ok' so ANY mint works, including mint.minibits.cash
- useCashu default mint: native prefers minibits (matches skatespots@minibits.cash brand identity) then coinos/21mint; browser uses CORS-friendly candidates
- MintPrivacyManager recommended list is platform-aware: native leads with Minibits (recommended), web leads with Coinos and flags Minibits as native-only
- CashuWallet: 'This mint can't be reached from your browser' banner for the browser CORS case (never shows on native), pointing users to a web-friendly mint or the native app
- Lightning invoice error mentions the native-app option

Same codebase now uses minibits directly when running as the native app, and a CORS-safe mint when running in a web browser.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:22:41 +07:00
shakespeare.diy
6331a937f2 feat: map as homepage, 3D globe, camera fix, Sasquatch upgrades, UI/UX polish
## 1. Map as Homepage
- `/` now renders MapPage (the globe/map) — removed old Index homepage
- `/home` redirects to Index (legacy), `/map` alias still works
- Navbar: removed HOME link, SPOTS (MapPin) is now first nav item
- isActive() treats both `/` and `/map` as active for SPOTS tab

## 2. 3D Globe View
- New SkateGlobe component using pure Three.js (no h3-js/CDN issues)
- Earth night texture with atmospheric neon red glow
- Spot pins (red) + shop pins (lime) on globe surface
- Click pin → zoom camera in + show info card
- Drag to rotate, scroll/pinch to zoom, auto-rotation when idle
- Globe toggle button added to map/list view switcher
- Selected marker ring + info card with View Spot/Shop links
- Lazy-loaded with Suspense skeleton

## 3. Settings → Stash Link
- Added My Stash card in Settings > Wallet tab
- Links to /stash with Package icon and description
- Smooth hover animation with ChevronRight

## 4. Camera Fix (Capacitor Native)
- CameraCapture now detects native Capacitor platform at runtime
- On native Android/iOS: uses @capacitor/camera (OS-level camera, no WebView permission popup)
- On browser: falls back to getUserMedia stream (existing behaviour)
- Added Upload from Gallery fallback when camera permission denied
- Better error handling for user cancellation

## 5. Sasquatch Upgrades
- FAQ page: replaced static accordion wall with Sasquatch-powered UI
  - Quick question chips that pre-load questions into Sasquatch
  - "Open The Sasquatch" CTA button
  - Quick facts grid, clean modern design
- About page: added Sasquatch CTA card at bottom
- SasquatchGuide: listens for sasquatch:open and sasquatch:ask custom events
  - External pages can now trigger and pre-fill the Sasquatch
  - Sasquatch now shows even without a page-level message prop (for external triggers)

## 6. UI/UX Audit & Polish
- Focus rings: on-brand red (WCAG accessible)
- Card hover micro-interactions: subtle translateY + shadow
- Button/link transition consistency (150ms ease)
- Reduced motion support (@media prefers-reduced-motion)
- Modal backdrop blur improvement
- Skeleton shimmer animation utility
- Globe CSS utilities (touch-action: none for canvas)
- Typography helpers (text-balance, text-pretty)
- Improved map-full-height responsive calculation

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-22 22:47:39 +07:00
shakespeare.diy
f010a61608 fix: use nostr-signer-capacitor-plugin for Amber NIP-55 login
All previous URL-scheme approaches (nostrsigner:, intent:) were fundamentally
wrong for a Capacitor app. JavaScript in a WebView cannot reliably dispatch
Android intents via URL navigation.

The correct solution — used by Coracle and other Capacitor-based Nostr apps —
is the nostr-signer-capacitor-plugin which communicates with Amber via native
Android intents through the Capacitor bridge:

  NostrSignerPlugin.setPackageName({ packageName: 'com.greenart7c3.nostrsigner' })
  NostrSignerPlugin.isExternalSignerInstalled()
  NostrSignerPlugin.getPublicKey() → { npub }

This is synchronous from the JS side, handles the entire Android intent
roundtrip natively, and returns the pubkey directly. No URL encoding,
no callback URLs, no redirects, no malformed request errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:59:04 +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
fad3a3323d Replace Impact with Bebas Neue + Barlow Condensed for authentic skate typography
- Install @fontsource/bebas-neue and @fontsource/barlow-condensed
- Add .font-display (Bebas Neue) and .font-condensed (Barlow Condensed) CSS utilities
- Hero headline: split into two lines SKATE / SPOTS with clamp() fluid sizing,
  Bebas Neue replaces Impact throughout
- Subtitle: Barlow Condensed 700 with wide letter-spacing
- Description: Barlow Condensed italic
- All section headings (Featured Spots, Events, The Lab, CTA, New Here?): Bebas Neue
- Section subheadings: Barlow Condensed with tracked uppercase
- Desktop + mobile header logo: Bebas Neue
- Footer logo: Bebas Neue
- Tagline badge: Barlow Condensed 600

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 00:37:32 +07:00
shakespeare.diy
046a109eca Fix card printing: html2canvas renders card to PNG before printing
The root cause of white/blank print: browsers strip CSS background gradients
and colors by default even with print-color-adjust: exact, especially inside
nested components wrapped by Layout.

Solution: use html2canvas to render the live card DOM to a canvas at 4x scale,
convert to PNG, then open a dedicated print window containing ONLY that image.
Images always print in full color — no CSS override needed.

Changes:
- Add html2canvas dependency
- renderCardToImage(): captures card DOM element at 4x scale with useCORS for
  cross-origin avatars, temporarily strips holo animation for clean capture
- handlePrint(): calls renderCardToImage, opens popup window with CSS @page
  set to 85.6×54mm, injects the PNG, auto-triggers print dialog
- handleDownload(): same render pipeline, saves as PNG file (for sharing
  digitally, WhatsApp, iMessage, or taking to a print shop)
- Added Download / Save PNG button alongside Print Card button
- Added Loader2 spinner on buttons while rendering
- Removed print-only-card div and broken @media print CSS (no longer needed)
- Clean up CSS print block to minimal fallback only
- Fix "on-chain proof" → "Signed proof" in badge dialog

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 15:22:13 +07:00
shakespeare.diy
f9327c952d fix: update license page tagline
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 13:27:00 +07:00
shakespeare.diy
451ec5d3ed Sync package.json and package-lock.json to exactly match skate-spots
- react-day-picker: ^9.0.0 → ^8.10.1 (match skate-spots)
- zod: ^3.23.8 → ^3.25.71 (match skate-spots)
- Copy package-lock.json verbatim from skate-spots for identical dependency resolution

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 23:25:43 +07:00
shakespeare.diy
85c2bdc5cc fix: replace radix-ui umbrella imports with direct @radix-ui/react-* packages
The shadcn/ui components were using the new "radix-ui" umbrella package
import style (e.g. `import { Tooltip as TooltipPrimitive } from "radix-ui"`).
However, radix-ui@1.5.0 re-exports ALL radix primitives including ones not
installed (@radix-ui/react-accessible-icon, @radix-ui/react-form,
@radix-ui/react-one-time-password-field, @radix-ui/react-password-toggle-field,
@radix-ui/react-toolbar), causing esbuild to fail with 5 unresolved import
errors outside Shakespeare.

Fix: rewrite all 32 imports across the ui/ components to import directly
from their individual @radix-ui/react-* packages (which are all already
installed). Remove the radix-ui umbrella package entirely from package.json
and package-lock.json.

Affected files (32 import changes):
accordion, alert-dialog, aspect-ratio, avatar, badge, breadcrumb, button,
checkbox, collapsible, context-menu, dialog, dropdown-menu, form, hover-card,
label, menubar, navigation-menu, popover, progress, radio-group, scroll-area,
select, separator, sheet, sidebar, slider, switch, tabs, toggle-group, toggle,
tooltip

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:01:07 +07:00
shakespeare.diy
9b58f412cd fix: add missing radix-ui umbrella package dependency
All shadcn/ui components in src/components/ui/ were already written against
the new shadcn convention of importing from the "radix-ui" umbrella package
(e.g. `import { Tooltip as TooltipPrimitive } from "radix-ui"`), but
"radix-ui" was never declared in package.json — only the individual
@radix-ui/* packages were listed.

This caused `npm i` + `vite build` to fail outside Shakespeare with:
  [plugin:vite:import-analysis] Failed to resolve import "radix-ui"

Fix: add `"radix-ui": "^1.0.1"` to dependencies (resolved to 1.5.0 in
the lock file). No code changes needed.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 21:52:11 +07:00
shakespeare.diy
93e607ea1f fix: upgrade react-day-picker to v9 to resolve date-fns v4 peer dep conflict
react-day-picker@8 requires date-fns@^2||^3, which conflicts with the
project's date-fns@^4.1.0. react-day-picker@9 dropped the date-fns peer
dependency entirely (bundles its own date helpers), so `npm i` now resolves
cleanly without --legacy-peer-deps.

- package.json: ^8.10.1 → ^9.0.0
- package-lock.json: updated version/resolved/integrity/peerDependencies
  for node_modules/react-day-picker to 9.14.0

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 21:42:42 +07:00
shakespeare.diy
27e14994b5 Import files from skate-spots project
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 20:04:15 +07:00
shakespeare.diy
06fae294b8 New project created with Shakespeare
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 19:49:07 +07:00