1- import React , { useEffect , Component , ReactNode } from "react" ;
1+ import React , { useEffect , Component , ReactNode , useMemo , useCallback } from "react" ;
22import { useRouter } from "next/router" ;
33import Link from "next/link" ;
44import { useNostrChat } from "@jinglescode/nostr-chat-plugin" ;
@@ -32,6 +32,7 @@ import { MobileNavigation } from "@/components/ui/mobile-navigation";
3232import { MobileActionsMenu } from "@/components/ui/mobile-actions-menu" ;
3333import { Button } from "@/components/ui/button" ;
3434import { Card , CardContent } from "@/components/ui/card" ;
35+ import { cn } from "@/lib/utils" ;
3536
3637// Dynamically import ConnectWallet with SSR disabled to avoid production SSR issues
3738// Using a version-based key ensures fresh mount on updates, preventing cache issues
@@ -127,23 +128,68 @@ export default function RootLayout({
127128 } , [ ] ) ;
128129
129130 const { mutate : createUser } = api . user . createUser . useMutation ( {
131+ onMutate : async ( variables ) => {
132+ // Cancel outgoing refetches
133+ await ctx . user . getUserByAddress . cancel ( { address : variables . address } ) ;
134+
135+ // Snapshot previous value
136+ const previous = ctx . user . getUserByAddress . getData ( { address : variables . address } ) ;
137+
138+ // Optimistically update
139+ ctx . user . getUserByAddress . setData (
140+ { address : variables . address } ,
141+ ( old ) => old ?? {
142+ address : variables . address ,
143+ stakeAddress : variables . stakeAddress ,
144+ drepKeyHash : variables . drepKeyHash ?? "" ,
145+ nostrKey : variables . nostrKey ,
146+ createdAt : new Date ( ) ,
147+ updatedAt : new Date ( ) ,
148+ }
149+ ) ;
150+
151+ return { previous } ;
152+ } ,
153+ onError : ( err , variables , context ) => {
154+ // Rollback on error
155+ if ( context ?. previous ) {
156+ ctx . user . getUserByAddress . setData ( { address : variables . address } , context . previous ) ;
157+ }
158+ console . error ( "Error creating user:" , err ) ;
159+ } ,
130160 onSuccess : ( _ , variables ) => {
131161 console . log ( "User created/updated successfully, invalidating user query" ) ;
132- // Invalidate the user query so it refetches the newly created user
162+ // Invalidate to ensure we have the latest data
133163 void ctx . user . getUserByAddress . invalidate ( { address : variables . address } ) ;
134164 } ,
135- onError : ( e ) => {
136- console . error ( "Error creating user:" , e ) ;
137- } ,
138165 } ) ;
139166 const { mutate : updateUser } = api . user . updateUser . useMutation ( {
167+ onMutate : async ( variables ) => {
168+ // Cancel outgoing refetches
169+ await ctx . user . getUserByAddress . cancel ( { address : variables . address } ) ;
170+
171+ // Snapshot previous value
172+ const previous = ctx . user . getUserByAddress . getData ( { address : variables . address } ) ;
173+
174+ // Optimistically update
175+ ctx . user . getUserByAddress . setData (
176+ { address : variables . address } ,
177+ ( old ) => old ? { ...old , ...variables , updatedAt : new Date ( ) } : old
178+ ) ;
179+
180+ return { previous } ;
181+ } ,
182+ onError : ( err , variables , context ) => {
183+ // Rollback on error
184+ if ( context ?. previous ) {
185+ ctx . user . getUserByAddress . setData ( { address : variables . address } , context . previous ) ;
186+ }
187+ console . error ( "Error updating user:" , err ) ;
188+ } ,
140189 onSuccess : ( _ , variables ) => {
141190 console . log ( "User updated successfully, invalidating user query" ) ;
142191 void ctx . user . getUserByAddress . invalidate ( { address : variables . address } ) ;
143192 } ,
144- onError : ( e ) => {
145- console . error ( "Error updating user:" , e ) ;
146- } ,
147193 } ) ;
148194
149195 // Sync address from hook to store
@@ -196,12 +242,13 @@ export default function RootLayout({
196242 initializeWallet ( ) ;
197243 } , [ connected , activeWallet , user , address , createUser , generateNsec ] ) ;
198244
199- const isWalletPath = router . pathname . includes ( "/wallets/[wallet]" ) ;
200- const walletPageRoute = router . pathname . split ( "/wallets/[wallet]/" ) [ 1 ] ;
201- const walletPageNames = walletPageRoute ? walletPageRoute . split ( "/" ) : [ ] ;
202- const pageIsPublic = publicRoutes . includes ( router . pathname ) ;
203- const isLoggedIn = ! ! user ;
204- const isHomepage = router . pathname === "/" ;
245+ // Memoize computed route values
246+ const isWalletPath = useMemo ( ( ) => router . pathname . includes ( "/wallets/[wallet]" ) , [ router . pathname ] ) ;
247+ const walletPageRoute = useMemo ( ( ) => router . pathname . split ( "/wallets/[wallet]/" ) [ 1 ] , [ router . pathname ] ) ;
248+ const walletPageNames = useMemo ( ( ) => walletPageRoute ? walletPageRoute . split ( "/" ) : [ ] , [ walletPageRoute ] ) ;
249+ const pageIsPublic = useMemo ( ( ) => publicRoutes . includes ( router . pathname ) , [ router . pathname ] ) ;
250+ const isLoggedIn = useMemo ( ( ) => ! ! user , [ user ] ) ;
251+ const isHomepage = useMemo ( ( ) => router . pathname === "/" , [ router . pathname ] ) ;
205252
206253 // Keep track of the last visited wallet to show wallet menu even on other pages
207254 const [ lastVisitedWalletId , setLastVisitedWalletId ] = React . useState < string | null > ( null ) ;
@@ -224,7 +271,7 @@ export default function RootLayout({
224271 }
225272 } , [ router . query . wallet , isWalletPath , appWallet , multisigWallet ] ) ;
226273
227- const clearWalletContext = React . useCallback ( ( ) => {
274+ const clearWalletContext = useCallback ( ( ) => {
228275 setLastVisitedWalletId ( null ) ;
229276 setLastVisitedWalletName ( null ) ;
230277 setLastWalletStakingEnabled ( null ) ;
@@ -237,11 +284,25 @@ export default function RootLayout({
237284 }
238285 } , [ isHomepage , lastVisitedWalletId , clearWalletContext ] ) ;
239286
240- const showWalletMenu = isLoggedIn && ( isWalletPath || ! ! lastVisitedWalletId ) ;
287+ // Memoize computed values
288+ const showWalletMenu = useMemo ( ( ) => isLoggedIn && ( isWalletPath || ! ! lastVisitedWalletId ) , [ isLoggedIn , isWalletPath , lastVisitedWalletId ] ) ;
289+
290+ // Don't show background loading when wallet is connecting or just connected (button already shows spinner)
291+ // The connect button shows a spinner when: connecting OR (connected && (!user || userLoading))
292+ const isConnecting = useMemo ( ( ) => String ( walletState ) === String ( WalletState . CONNECTING ) , [ walletState ] ) ;
293+ const isButtonShowingSpinner = useMemo ( ( ) => isConnecting || ( connected && ( ! user || isLoading ) ) , [ isConnecting , connected , user , isLoading ] ) ;
294+ const shouldShowBackgroundLoading = useMemo ( ( ) => isLoading && ! isButtonShowingSpinner , [ isLoading , isButtonShowingSpinner ] ) ;
295+
296+ // Memoize wallet ID for menu
297+ const walletIdForMenu = useMemo ( ( ) => ( router . query . wallet as string ) || lastVisitedWalletId || undefined , [ router . query . wallet , lastVisitedWalletId ] ) ;
241298
242299 return (
243300 < div className = "flex h-screen w-screen flex-col overflow-hidden" >
244- { isLoading && < Loading /> }
301+ { shouldShowBackgroundLoading && (
302+ < div className = "fixed inset-0 z-50 transition-opacity duration-300 ease-in-out opacity-100" >
303+ < Loading />
304+ </ div >
305+ ) }
245306
246307 { /* Header - full width, always on top */ }
247308 < header
@@ -250,11 +311,11 @@ export default function RootLayout({
250311 >
251312 < div className = "flex h-14 items-center gap-4 lg:h-16" >
252313 { /* Mobile menu button - hidden only on public homepage (not logged in) */ }
253- { ( isLoggedIn || ! isHomepage ) && (
314+ { ( isLoggedIn || ! isHomepage ) && (
254315 < MobileNavigation
255316 showWalletMenu = { showWalletMenu }
256317 isLoggedIn = { isLoggedIn }
257- walletId = { router . query . wallet as string || lastVisitedWalletId || undefined }
318+ walletId = { walletIdForMenu }
258319 fallbackWalletName = { lastVisitedWalletName }
259320 onClearWallet = { clearWalletContext }
260321 stakingEnabled = { lastWalletStakingEnabled ?? undefined }
@@ -342,7 +403,7 @@ export default function RootLayout({
342403 { showWalletMenu && (
343404 < div className = "mt-4" >
344405 < MenuWallet
345- walletId = { router . query . wallet as string || lastVisitedWalletId || undefined }
406+ walletId = { walletIdForMenu }
346407 stakingEnabled = { isWalletPath ? undefined : ( lastWalletStakingEnabled ?? undefined ) }
347408 />
348409 </ div >
0 commit comments