import { useState, useRef, useEffect, lazy, Suspense } from 'react'; import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { cn } from '@/lib/utils'; import { isNativePlatform } from '@/lib/platform'; import { useNavPreferences, useDesktopNavPreferences, type NavTab } from '@/hooks/useNavPreferences'; import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, SheetTrigger, SheetTitle } from '@/components/ui/sheet'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, } from '@/components/ui/dropdown-menu'; import { LoginArea } from '@/components/auth/LoginArea'; import { useSasquatchGuide } from '@/hooks/useSasquatchGuide'; import { LanguageToggle } from '@/components/LanguageToggle'; import { LANGUAGE_OPTIONS } from '@/lib/languageOptions'; import { clearTranslationMemory } from '@/lib/translation'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useTranslation } from '@/contexts/language'; import { useLoggedInAccounts } from '@/hooks/useLoggedInAccounts'; import { AuthDialogContext } from '@/contexts/authDialog'; import { MapPin, Calendar, Menu, Wallet, Settings, GraduationCap, BarChart3, MoreHorizontal, Info, Bookmark, MessageCircle, LogOut, User, Plus, CheckCircle2, Package, Heart, CreditCard, Radio, Users, SlidersHorizontal, BookOpen, PersonStanding, Languages, MessagesSquare, ChevronLeft, ChevronRight, ChevronDown, ArrowLeft, Search, } from 'lucide-react'; import { OfflineIndicator } from '@/components/OfflineIndicator'; import { UserStatusIndicator } from '@/components/UserStatusIndicator'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; import { NewsletterOverlay } from '@/components/NewsletterOverlay'; import { NotificationBellLazy } from '@/components/NotificationBellLazy'; import { GlobalSearch } from '@/components/GlobalSearch'; import { openGlobalSearch, GLOBAL_SEARCH_EVENT } from '@/lib/globalSearch'; import { OsNotificationBridge } from '@/components/OsNotificationBridge'; // Cashu balance widget is lazy — see components/wallet/CashuBalance.tsx for why // (keeps the Cashu stack out of the initial bundle). const LazyCashuBalanceText = lazy(() => import('@/components/wallet/CashuBalance').then((m) => ({ default: m.CashuBalanceText })), ); const LazyCashuBalanceBadge = lazy(() => import('@/components/wallet/CashuBalance').then((m) => ({ default: m.CashuBalanceBadge })), ); const LazyCashuMenuBalance = lazy(() => import('@/components/wallet/CashuBalance').then((m) => ({ default: m.CashuMenuBalance })), ); // The Sasquatch assistant is a floating overlay — not needed at first paint. const LazySasquatchGuide = lazy(() => import('@/components/SasquatchGuide').then((m) => ({ default: m.SasquatchGuide })), ); // On-demand modals — only rendered after the user opens them, so keep them out // of the always-loaded Layout chunk. const LazyCustomizeNavSheet = lazy(() => import('@/components/CustomizeNavSheet').then((m) => ({ default: m.CustomizeNavSheet })), ); const LazySetStatusDialog = lazy(() => import('@/components/SetStatusDialog').then((m) => ({ default: m.SetStatusDialog })), ); const LazyLoginDialog = lazy(() => import('@/components/auth/LoginDialog').then((m) => ({ default: m.LoginDialog })), ); const LazySignupDialog = lazy(() => import('@/components/auth/SignupDialog')); const LazyBackupReminder = lazy(() => import('@/components/BackupReminder').then((m) => ({ default: m.BackupReminder })), ); /** * Cheap synchronous hint that a Cashu wallet MIGHT exist, used to decide * whether to even load the (large) wallet chunk. On native the seed lives in * the OS KeyStore which we can't query synchronously, so we always load there; * on web `useCashu` persists the mnemonic in localStorage under this key. */ function hasCashuWalletHint(): boolean { if (isNativePlatform()) return true; try { return !!localStorage.getItem('cashu-mnemonic'); } catch { return false; } } /** * Page-level Suspense fallback. Keeps the header and bottom nav mounted while a * lazy route chunk loads, so tapping a menu item never blanks the whole app. * The fade is delayed slightly: a fast chunk resolves before it becomes visible. */ function RouteLoadingFallback() { return (