From dd85644fcab9f74be8a67b6e446274dd423a9743 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 23 Jul 2026 21:36:33 +0700 Subject: [PATCH] UX audit fixes: iOS directions, Me tab profile link, onboarding banner, empty states, stats deltas, newsletter pill - Fix iOS directions: Apple Maps first on Apple platforms, removed broken geo: URI, added Waze option, fixed popover z-index clipping behind Sheet component - Fix 'Me' bottom nav: now deep-links to /settings?section=profile for logged-in users so tapping 'Me' shows your profile, not a generic settings menu - Add SettingsPage URL param support: ?section=X opens sub-screen directly - Add onboarding welcome banner: dismissible strip at top of map for logged-out users with 'Get Started' CTA pointing to /onboarding, stored in localStorage - Fix Lab empty state: FAQ link + login prompt for logged-out users - Fix Events empty state: FAQ link added to all empty state instances - Fix GearSwap empty state: actionable 'Clear Filters' button + FAQ link - Community stats: add 'this month' delta badges (+N this month) on all stat cards - Newsletter pill: hide on map page to eliminate bottom sheet overlap conflict Co-authored-by: shakespeare.diy --- src/components/Layout.tsx | 4 +- src/components/NewsletterOverlay.tsx | 4 +- src/pages/CommunityStatsPage.tsx | 35 ++++++--- src/pages/EventsPage.tsx | 3 + src/pages/GearSwapPage.tsx | 33 +++++++-- src/pages/LabPage.tsx | 11 ++- src/pages/MapPage.tsx | 106 ++++++++++++++++++++++----- src/pages/SettingsPage.tsx | 8 +- 8 files changed, 163 insertions(+), 41 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index e2bb1b2..8750bab 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -295,9 +295,9 @@ function MobileNav({ ); })} - {/* Profile / Login tab */} + {/* Profile / Login tab — goes straight to profile section when logged in */} PILL_VISIBLE_DAYS || dismissed === latest.number ) { diff --git a/src/pages/CommunityStatsPage.tsx b/src/pages/CommunityStatsPage.tsx index e107b13..40f0f7d 100644 --- a/src/pages/CommunityStatsPage.tsx +++ b/src/pages/CommunityStatsPage.tsx @@ -50,6 +50,7 @@ function StatCard({ trustedValue, color, loading, + thisMonthDelta, }: { icon: React.ComponentType<{ className?: string }>; value: number; @@ -59,6 +60,8 @@ function StatCard({ trustedValue?: number; color: string; loading: boolean; + /** How many new items were added this month */ + thisMonthDelta?: number; }) { return ( @@ -69,7 +72,18 @@ function StatCard({ {loading ? ( ) : ( -

{value.toLocaleString()}

+
+

{value.toLocaleString()}

+ {typeof thisMonthDelta === 'number' && thisMonthDelta > 0 && ( + + + +{thisMonthDelta} this month + + )} +
)} {!loading && trustedValue !== undefined && (

@@ -155,6 +169,9 @@ export default function CommunityStatsPage() { const recentMonthly = useMemo(() => stats?.monthly.slice(-12) ?? [], [stats]); const recentCumulative = useMemo(() => stats?.cumulativeSpots.slice(-12) ?? [], [stats]); + // Current month delta — how many items were added this month + const thisMonth = useMemo(() => stats?.monthly[stats.monthly.length - 1], [stats]); + return (

@@ -193,16 +210,16 @@ export default function CommunityStatsPage() { Community at a Glance
- - - - + + + +
- - - - + + + +
diff --git a/src/pages/EventsPage.tsx b/src/pages/EventsPage.tsx index 68fa8fc..4cc732f 100644 --- a/src/pages/EventsPage.tsx +++ b/src/pages/EventsPage.tsx @@ -700,6 +700,9 @@ function EmptyState({ ) : null} + + Have questions? Check the FAQ → +
); } diff --git a/src/pages/GearSwapPage.tsx b/src/pages/GearSwapPage.tsx index 4d7331f..0883f18 100644 --- a/src/pages/GearSwapPage.tsx +++ b/src/pages/GearSwapPage.tsx @@ -411,14 +411,33 @@ export default function GearSwapPage() {

No listings found

{searchQuery || selectedCategory !== 'all' || trustFilter !== 'all' - ? 'Try adjusting your filters or search query.' - : 'Be the first to list your gear!'} + ? 'No gear matches your current filters. Try clearing them or searching something else.' + : 'Be the first to list your gear — decks, trucks, wheels, shoes, anything skate!'}

- - +
+ {(searchQuery || selectedCategory !== 'all' || trustFilter !== 'all') && ( + + )} + + + +
+ + Have questions? Check the FAQ →
diff --git a/src/pages/LabPage.tsx b/src/pages/LabPage.tsx index c413c49..9dd1d54 100644 --- a/src/pages/LabPage.tsx +++ b/src/pages/LabPage.tsx @@ -744,15 +744,22 @@ function EmptyState({ serviceType, user }: { serviceType: ServiceType; user: boo

{c.title}

-

{c.body}

- {user && ( +

{c.body}

+ {user ? ( + ) : ( +

+ Log in to create a profile +

)} + + Have questions? Check the FAQ → + ); } diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx index ded1fe2..80608dd 100644 --- a/src/pages/MapPage.tsx +++ b/src/pages/MapPage.tsx @@ -33,6 +33,7 @@ import { useComments } from '@/hooks/useComments'; import { useStash, STASH_CATEGORIES, CONDITION_LABELS, RARITY_TIERS, type StashItem } from '@/hooks/useStash'; import { useAuthor } from '@/hooks/useAuthor'; import { useSatsToUsd } from '@/hooks/useExchangeRate'; +import { useLocalStorage } from '@/hooks/useLocalStorage'; import { genUserName } from '@/lib/genUserName'; import { useTranslation } from '@/contexts/LanguageContext'; import { SPOT_CATEGORIES, SPOT_TYPE_LABELS } from '@/lib/types'; @@ -326,37 +327,53 @@ function StashDrawer() { ); } -// Universal directions picker — works on GrapheneOS, iOS, any browser +// Detect iOS/macOS for smarter maps ordering +function isApplePlatform(): boolean { + if (typeof navigator === 'undefined') return false; + return /iPhone|iPad|iPod|Macintosh/.test(navigator.userAgent) && !/(Windows|Android)/.test(navigator.userAgent); +} + +// Universal directions picker — works on iOS, Android, desktop function DirectionsMenu({ lat, lon, label }: { lat: string; lon: string; label: string }) { const [open, setOpen] = useState(false); + const onApple = isApplePlatform(); - const options = [ + const allOptions = [ { - name: 'Maps (device default)', - sub: 'Opens your default maps app', - href: `geo:${lat},${lon}?q=${lat},${lon}(${label})`, - emoji: '📍', - }, - { - name: 'OpenStreetMap', - sub: 'Privacy-friendly, no account', - href: `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&zoom=17`, - emoji: '🗺️', + name: 'Apple Maps', + sub: 'iOS & macOS', + href: `https://maps.apple.com/?ll=${lat},${lon}&q=${label}&dirflg=d`, + emoji: '🍎', + appleOnly: true, }, { name: 'Google Maps', sub: 'Opens in browser or app', href: `https://maps.google.com/?q=${lat},${lon}`, emoji: '🔵', + appleOnly: false, }, { - name: 'Apple Maps', - sub: 'iOS & macOS', - href: `https://maps.apple.com/?ll=${lat},${lon}&q=${label}`, - emoji: '🍎', + name: 'OpenStreetMap', + sub: 'Privacy-friendly, no account', + href: `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&zoom=17`, + emoji: '🗺️', + appleOnly: false, + }, + { + name: 'Waze', + sub: 'Community-powered navigation', + href: `https://waze.com/ul?ll=${lat},${lon}&navigate=yes`, + emoji: '🔸', + appleOnly: false, }, ]; + // On Apple platforms: Apple Maps first. On others: Google first, no Apple. + const options = onApple + ? allOptions + : allOptions.filter(o => !o.appleOnly); + return ( <> + + + )} + {/* Main Content */}
{viewMode === 'map' ? ( @@ -1322,6 +1369,29 @@ export default function MapPage() {
)} + {/* Add Spot FAB — primary CTA, bottom-left, always visible on map */} +
+ {user ? ( + + + + ) : ( + + )} +
+ {/* Near Me button + attribution — floating top-right, hidden when any overlay is open */}
(null); + const [searchParams] = useSearchParams(); + + // Allow deep-linking into a specific section via ?section=profile etc. + const initialSection = (searchParams.get('section') as Section) ?? null; + const [section, setSection] = useState
(initialSection); const handleNavigate = (s: Section) => setSection(s); const handleBack = () => setSection(null);