perf(bundle): lazy on-demand modals in Layout; skip the Cashu chunk without a wallet
- BackupReminder, CustomizeNavSheet, SetStatusDialog and LoginDialog are only needed after the user acts -> React.lazy (Layout chunk 511 KB -> 486 KB) - The Cashu balance widget now only mounts when a wallet likely exists (web: localStorage hint; native: always, since the seed is in the KeyStore), so wallet-less visitors never download the 245 KB Cashu chunk
This commit is contained in:
parent
8cf35cf7db
commit
81eeba7797
1 changed files with 68 additions and 28 deletions
|
|
@ -2,7 +2,7 @@ import { useState, useRef, lazy, Suspense } from 'react';
|
|||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CustomizeNavSheet } from '@/components/CustomizeNavSheet';
|
||||
import { isNativePlatform } from '@/lib/platform';
|
||||
import { useNavPreferences, useDesktopNavPreferences, type NavTab } from '@/hooks/useNavPreferences';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
|
@ -15,9 +15,7 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { LoginArea } from '@/components/auth/LoginArea';
|
||||
import { LoginDialog } from '@/components/auth/LoginDialog';
|
||||
import { useSasquatchGuide } from '@/hooks/useSasquatchGuide';
|
||||
import { BackupReminder } from '@/components/BackupReminder';
|
||||
import { LanguageToggle } from '@/components/LanguageToggle';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useTranslation } from '@/contexts/LanguageContext';
|
||||
|
|
@ -54,7 +52,6 @@ import {
|
|||
} 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';
|
||||
|
|
@ -76,6 +73,35 @@ const LazyCashuMenuBalance = lazy(() =>
|
|||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -371,9 +397,11 @@ function MobileNav({
|
|||
{isWallet ? (
|
||||
<>
|
||||
<Wallet className={cn('h-[22px] w-[22px] transition-transform duration-300', active ? 'scale-110' : 'group-active:scale-95')} />
|
||||
<Suspense fallback={null}>
|
||||
<LazyCashuBalanceBadge />
|
||||
</Suspense>
|
||||
{hasCashuWalletHint() && (
|
||||
<Suspense fallback={null}>
|
||||
<LazyCashuBalanceBadge />
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<tab.icon className={cn('h-[22px] w-[22px] transition-transform duration-300', active ? 'scale-110' : 'group-active:scale-95')} />
|
||||
|
|
@ -657,9 +685,11 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
)}
|
||||
>
|
||||
<Wallet className="h-5 w-5" />
|
||||
<Suspense fallback={null}>
|
||||
<LazyCashuBalanceText />
|
||||
</Suspense>
|
||||
{hasCashuWalletHint() && (
|
||||
<Suspense fallback={null}>
|
||||
<LazyCashuBalanceText />
|
||||
</Suspense>
|
||||
)}
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
|
@ -778,9 +808,11 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
<p className="text-xs text-muted-foreground">Ecash</p>
|
||||
</div>
|
||||
</div>
|
||||
<Suspense fallback={null}>
|
||||
<LazyCashuMenuBalance />
|
||||
</Suspense>
|
||||
{hasCashuWalletHint() && (
|
||||
<Suspense fallback={null}>
|
||||
<LazyCashuMenuBalance />
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
@ -973,8 +1005,10 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
</div>
|
||||
</header>
|
||||
|
||||
{/* Backup Reminder */}
|
||||
<BackupReminder />
|
||||
{/* Backup Reminder — lazy (on-demand banner) */}
|
||||
<Suspense fallback={null}>
|
||||
<LazyBackupReminder />
|
||||
</Suspense>
|
||||
|
||||
{/* Main Content — extra bottom padding on mobile so the fixed bottom nav never overlaps */}
|
||||
{/* noPadding: full-bleed pages (map) manage their own height */}
|
||||
|
|
@ -1048,8 +1082,10 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
middleTabs={middleTabs}
|
||||
/>
|
||||
|
||||
{/* Bottom-nav customization sheet (mobile) */}
|
||||
<CustomizeNavSheet open={showCustomizeNav} onOpenChange={setShowCustomizeNav} />
|
||||
{/* Bottom-nav customization sheet (mobile) — lazy (on-demand modal) */}
|
||||
<Suspense fallback={null}>
|
||||
<LazyCustomizeNavSheet open={showCustomizeNav} onOpenChange={setShowCustomizeNav} />
|
||||
</Suspense>
|
||||
|
||||
{/* Sasquatch Guide (all screens) — lazy; not needed at first paint */}
|
||||
<Suspense fallback={null}>
|
||||
|
|
@ -1059,18 +1095,22 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* Set Status Dialog */}
|
||||
<SetStatusDialog
|
||||
open={showStatusDialog}
|
||||
onOpenChange={setShowStatusDialog}
|
||||
/>
|
||||
{/* Set Status Dialog — lazy (on-demand modal) */}
|
||||
<Suspense fallback={null}>
|
||||
<LazySetStatusDialog
|
||||
open={showStatusDialog}
|
||||
onOpenChange={setShowStatusDialog}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* Add-another-account (multi-account switch on mobile) */}
|
||||
<LoginDialog
|
||||
isOpen={showAddAccount}
|
||||
onClose={() => setShowAddAccount(false)}
|
||||
onLogin={() => setShowAddAccount(false)}
|
||||
/>
|
||||
{/* Add-another-account (multi-account switch on mobile) — lazy */}
|
||||
<Suspense fallback={null}>
|
||||
<LazyLoginDialog
|
||||
isOpen={showAddAccount}
|
||||
onClose={() => setShowAddAccount(false)}
|
||||
onLogin={() => setShowAddAccount(false)}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* Newsletter: floating pill + Monday admin reminder */}
|
||||
<NewsletterOverlay />
|
||||
|
|
|
|||
Loading…
Reference in a new issue