diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 64e23a5..ea0b39e 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from 'react'; +import { useState, useRef, lazy, Suspense } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { cn } from '@/lib/utils'; @@ -20,7 +20,6 @@ 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'; @@ -63,6 +62,18 @@ import { genUserName } from '@/lib/genUserName'; import { NewsletterOverlay } from '@/components/NewsletterOverlay'; import { NotificationBell } from '@/components/NotificationBell'; +// 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 })), +); + interface LayoutProps { children: React.ReactNode; /** If true, removes the bottom padding that prevents bottom nav overlap. @@ -79,17 +90,6 @@ const navItemKeys = [ { href: '/crews', labelKey: 'nav.crews', icon: Users }, ]; -// 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, @@ -288,7 +288,6 @@ function MobileHeaderUserMenu({ function MobileNav({ isActive, - cashuBalance, isLoggedIn, userPubkey, userAvatar, @@ -299,7 +298,6 @@ function MobileNav({ middleTabs, }: { isActive: (path: string) => boolean; - cashuBalance: number; isLoggedIn: boolean; userPubkey?: string; userAvatar?: string; @@ -370,11 +368,9 @@ function MobileNav({ {isWallet ? ( <> - {cashuBalance > 0 && ( - - {formatSatsCompact(cashuBalance)} - - )} + + + ) : ( @@ -474,7 +470,6 @@ export function Layout({ children, noPadding }: LayoutProps) { const [showAddAccount, setShowAddAccount] = 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 || ''); @@ -659,11 +654,9 @@ export function Layout({ children, noPadding }: LayoutProps) { )} > - {hasCashu && cashuBalance > 0 && ( - - {formatSatsCompact(cashuBalance)} - - )} + + + )} @@ -782,14 +775,9 @@ export function Layout({ children, noPadding }: LayoutProps) {

Ecash

- {hasCashu && ( -
-

- {cashuBalance.toLocaleString()} -

-

sats

-
- )} + + + @@ -1047,7 +1035,6 @@ export function Layout({ children, noPadding }: LayoutProps) { {/* Mobile Bottom Navigation */} = 1_000_000) return `${(sats / 1_000_000).toFixed(1)}M`; + if (sats >= 1000) return `${(sats / 1000).toFixed(sats >= 10000 ? 0 : 1)}k`; + return sats.toString(); +} + +/** Desktop header: sats next to the wallet nav button. */ +export function CashuBalanceText() { + const { totalBalance, isInitialized } = useCashu(); + if (!isInitialized || totalBalance <= 0) return null; + return {formatSatsCompact(totalBalance)}; +} + +/** Mobile bottom nav: small badge on the wallet tab. */ +export function CashuBalanceBadge() { + const { totalBalance, isInitialized } = useCashu(); + if (!isInitialized || totalBalance <= 0) return null; + return ( + + {formatSatsCompact(totalBalance)} + + ); +} + +/** Mobile menu: the "Cashu / Ecash" row's balance. */ +export function CashuMenuBalance() { + const { totalBalance, isInitialized } = useCashu(); + if (!isInitialized) return null; + return ( +
+

{totalBalance.toLocaleString()}

+

sats

+
+ ); +} diff --git a/src/hooks/useNWC.ts b/src/hooks/useNWC.ts index 43b14c7..f411721 100644 --- a/src/hooks/useNWC.ts +++ b/src/hooks/useNWC.ts @@ -1,7 +1,7 @@ import { useState, useCallback } from 'react'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useToast } from '@/hooks/useToast'; -import { LN } from '@getalby/sdk'; +import type { LN } from '@getalby/sdk'; export interface NWCConnection { connectionString: string; @@ -61,6 +61,8 @@ export function useNWCInternal() { } try { + // Load the Getalby SDK on demand so it stays out of the initial bundle. + const { LN } = await import('@getalby/sdk'); let timeoutId: NodeJS.Timeout | undefined; const testPromise = new Promise((resolve, reject) => { try { @@ -165,6 +167,8 @@ export function useNWCInternal() { throw new Error('Invalid connection: missing connection string'); } + // Load the Getalby SDK on demand so it stays out of the initial bundle. + const { LN } = await import('@getalby/sdk'); let client: LN; try { client = new LN(connection.connectionString);