import { useState } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, } from '@/components/ui/dropdown-menu'; import { LoginArea } from '@/components/auth/LoginArea'; import { SasquatchGuide } from '@/components/SasquatchGuide'; import { useSasquatchGuide } from '@/hooks/useSasquatchGuide'; import { BackupReminder } from '@/components/BackupReminder'; import { LanguageToggle } from '@/components/LanguageToggle'; import { useCashu } from '@/hooks/wallet/useCashu'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useTranslation } from '@/contexts/LanguageContext'; import { useLoggedInAccounts } from '@/hooks/useLoggedInAccounts'; import { MapPin, Calendar, Menu, Home, Wallet, Settings, GraduationCap, BarChart3, MoreHorizontal, Info, Bookmark, MessageCircle, LogOut, User, LogIn, Plus, CheckCircle2, Database, Package, Zap, Heart, CreditCard, Radio, } from 'lucide-react'; import { OfflineIndicator } from '@/components/OfflineIndicator'; import { UserStatusIndicator } from '@/components/UserStatusIndicator'; import { SetStatusDialog } from '@/components/SetStatusDialog'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; import { NewsletterOverlay } from '@/components/NewsletterOverlay'; interface LayoutProps { children: React.ReactNode; /** If true, removes the bottom padding that prevents bottom nav overlap. * Use for full-bleed pages like the map that manage their own height. */ noPadding?: boolean; } // Nav item keys that map to translation keys // HOME removed — the map IS the homepage now const navItemKeys = [ { href: '/', labelKey: 'nav.map', icon: MapPin }, { href: '/lab', labelKey: 'nav.lab', icon: GraduationCap }, { href: '/events', labelKey: 'nav.events', icon: Calendar }, ]; // Format sats for display (e.g., 1234 -> "1.2k", 12345 -> "12k") function formatSatsCompact(sats: number): string { if (sats >= 1000000) { return `${(sats / 1000000).toFixed(1)}M`; } if (sats >= 1000) { return `${(sats / 1000).toFixed(sats >= 10000 ? 0 : 1)}k`; } return sats.toString(); } function NavLink({ href, label, icon: Icon, isActive, onClick, }: { href: string; label: string; icon: React.ComponentType<{ className?: string }>; isActive: boolean; onClick?: () => void; }) { return ( {label} ); } // Mobile header user menu - shows when logged in with dropdown for actions function MobileHeaderUserMenu({ pubkey, onLogout, onSetStatus, }: { pubkey: string; onLogout: () => void; onSetStatus: () => void; }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; const displayName = metadata?.name ?? genUserName(pubkey); const avatar = metadata?.picture; return ( {/* User info header */}
{displayName.slice(0, 2).toUpperCase()}

{displayName}

Logged In

{/* Set Status - highlighted */} Set Status 🛹 Add Spot My Profile My Data My Stash Wallet & Stash Log Out
); } function MobileNav({ isActive, cashuBalance, isLoggedIn, userPubkey, userAvatar, userName, t, }: { isActive: (path: string) => boolean; cashuBalance: number; isLoggedIn: boolean; userPubkey?: string; userAvatar?: string; userName?: string; t: (key: string) => string; }) { // Decide which tabs to show — max 5 on small screens // Logged in: Home, Map, Lab, Events, Profile (wallet in profile dropdown) // Logged out: Home, Map, Lab, Events, Login const tabs = [ ...navItemKeys, ...(isLoggedIn ? [{ href: '/wallet', labelKey: 'nav.wallet', icon: Wallet }] : []), ]; return ( ); } export function Layout({ children, noPadding }: LayoutProps) { const location = useLocation(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [showStatusDialog, setShowStatusDialog] = useState(false); const { currentMessage, dismissCurrentMessage } = useSasquatchGuide(); const { user } = useCurrentUser(); const { totalBalance: cashuBalance, isInitialized: hasCashu } = useCashu(); const { t } = useTranslation(); const { currentUser: currentAccount, removeLogin } = useLoggedInAccounts(); const author = useAuthor(user?.pubkey || ''); const displayName = author.data?.metadata?.name ?? genUserName(user?.pubkey); const userAvatar = author.data?.metadata?.picture; const isActive = (path: string) => { // Both / and /map are the "spots" page if (path === '/') return location.pathname === '/' || location.pathname === '/map'; return location.pathname.startsWith(path); }; return (
{/* Desktop Header */}
SkateSpots SKATESPOTS
{/* Messages Quick Access - Desktop */} {user && ( )} {/* Collections Quick Access - Desktop */} {user && ( )} {/* Wallet Quick Access - Desktop */} {user && ( )} {/* Status Quick Access - Desktop */} {user && ( )} {/* Offline Indicator */} {/* Language Toggle */}
{/* Mobile Header */}
SkateSpots SKATESPOTS
{/* User menu when logged in / Login button when not */} {user ? ( removeLogin(currentAccount?.id || '')} onSetStatus={() => setShowStatusDialog(true)} /> ) : ( )}
{/* Wallet Card - Mobile Menu */} {user && ( setMobileMenuOpen(false)} className="block" >

{t('wallet.cashu.title')}

Ecash

{hasCashu && (

{cashuBalance.toLocaleString()}

sats

)}
)}
{navItemKeys.map((item) => ( setMobileMenuOpen(false)} /> ))} {user && ( setMobileMenuOpen(false)} /> )} {user && ( setMobileMenuOpen(false)} /> )} {user && ( setMobileMenuOpen(false)} /> )} setMobileMenuOpen(false)} /> setMobileMenuOpen(false)} /> setMobileMenuOpen(false)} /> setMobileMenuOpen(false)} /> setMobileMenuOpen(false)} /> setMobileMenuOpen(false)} />
{/* Language Toggle in Mobile Menu */}
{/* Login/Logout Section */}
{user ? ( <> {/* User Profile Card */}
{displayName.slice(0, 2).toUpperCase()}

{displayName}

Logged In

{/* Logout Button - Big and Clear */} ) : ( <> {/* Not Logged In - Clear CTA */}

Not Logged In

Log in to add spots, zap skaters, and save your favorites

)}
{/* Backup Reminder */} {/* Main Content — extra bottom padding on mobile so the fixed bottom nav never overlaps */} {/* noPadding: full-bleed pages (map) manage their own height */}
{children}
{/* Footer - Desktop */}
Made with ❤️ for the skateboarding community
Newsletter License Collaborators & Friends Not sponsored by OpenSats OpenSats
{/* No mobile footer - bottom nav provides navigation, keeps UI clean */} {/* Mobile Bottom Navigation */} {/* Sasquatch Guide (all screens) */} {/* Set Status Dialog */} {/* Newsletter: floating pill + Monday admin reminder */}
); }