@@ -9,7 +9,7 @@ import useUser from "@/hooks/useUser";
99import { useUserStore } from "@/lib/zustand/user" ;
1010import useAppWallet from "@/hooks/useAppWallet" ;
1111
12- import SessionProvider from "@/components/SessionProvider"
12+ import SessionProvider from "@/components/SessionProvider" ;
1313import { getServerSession } from "next-auth" ;
1414
1515import MenuWallets from "@/components/common/overall-layout/menus/wallets" ;
@@ -35,26 +35,33 @@ import {
3535 BreadcrumbSeparator ,
3636} from "@/components/ui/breadcrumb" ;
3737
38- // Simple error boundary component
39- class SimpleErrorBoundary extends Component <
38+ // Enhanced error boundary component for wallet errors
39+ class WalletErrorBoundary extends Component <
4040 { children : ReactNode ; fallback : ReactNode } ,
41- { hasError : boolean }
41+ { hasError : boolean ; error : Error | null }
4242> {
4343 constructor ( props : { children : ReactNode ; fallback : ReactNode } ) {
4444 super ( props ) ;
45- this . state = { hasError : false } ;
45+ this . state = { hasError : false , error : null } ;
4646 }
4747
48- static getDerivedStateFromError ( ) {
49- return { hasError : true } ;
48+ static getDerivedStateFromError ( error : Error ) {
49+ return { hasError : true , error } ;
5050 }
5151
5252 componentDidCatch ( error : Error , errorInfo : any ) {
53- console . error ( 'Error caught by boundary:' , error , errorInfo ) ;
53+ console . error ( 'Error caught by wallet boundary:' , error , errorInfo ) ;
54+
55+ // Handle specific wallet errors
56+ if ( error . message . includes ( "account changed" ) ) {
57+ console . log ( "Wallet account changed error caught by boundary, reloading page..." ) ;
58+ window . location . reload ( ) ;
59+ return ;
60+ }
5461 }
5562
5663 render ( ) {
57- if ( this . state . hasError ) {
64+ if ( this . state . hasError && this . state . error && ! this . state . error . message . includes ( "account changed" ) ) {
5865 return this . props . fallback ;
5966 }
6067 return this . props . children ;
@@ -75,44 +82,93 @@ export default function RootLayout({
7582 const userAddress = useUserStore ( ( state ) => state . userAddress ) ;
7683 const setUserAddress = useUserStore ( ( state ) => state . setUserAddress ) ;
7784
85+ // Global error handler for unhandled promise rejections
86+ useEffect ( ( ) => {
87+ const handleUnhandledRejection = ( event : PromiseRejectionEvent ) => {
88+ console . error ( 'Unhandled promise rejection:' , event . reason ) ;
89+
90+ // Handle wallet-related errors specifically
91+ if ( event . reason && typeof event . reason === 'object' ) {
92+ const error = event . reason as Error ;
93+ if ( error . message && error . message . includes ( "account changed" ) ) {
94+ console . log ( "Account changed error caught by global handler, reloading page..." ) ;
95+ event . preventDefault ( ) ; // Prevent the error from being logged to console
96+ window . location . reload ( ) ;
97+ return ;
98+ }
99+ }
100+ } ;
101+
102+ window . addEventListener ( 'unhandledrejection' , handleUnhandledRejection ) ;
103+
104+ return ( ) => {
105+ window . removeEventListener ( 'unhandledrejection' , handleUnhandledRejection ) ;
106+ } ;
107+ } , [ ] ) ;
108+
78109 const { mutate : createUser } = api . user . createUser . useMutation ( {
79110 onError : ( e ) => console . error ( e ) ,
80111 } ) ;
112+ const { mutate : updateUser } = api . user . updateUser . useMutation ( {
113+ onError : ( e ) => console . error ( e ) ,
114+ } ) ;
81115
82116 // Single effect for address + user creation
83117 useEffect ( ( ) => {
84118 ( async ( ) => {
85119 if ( ! connected || ! wallet ) return ;
86120
87- // 1) Set user address in store
88- let address = ( await wallet . getUsedAddresses ( ) ) [ 0 ] ;
89- if ( ! address ) address = ( await wallet . getUnusedAddresses ( ) ) [ 0 ] ;
90- if ( address ) setUserAddress ( address ) ;
121+ try {
122+ // 1) Set user address in store
123+ let address = ( await wallet . getUsedAddresses ( ) ) [ 0 ] ;
124+ if ( ! address ) address = ( await wallet . getUnusedAddresses ( ) ) [ 0 ] ;
125+ if ( address ) setUserAddress ( address ) ;
91126
92- // 2) If user doesn't exist, create it
93- if ( ! isLoading && user === null ) {
127+ // 2) Get stake address
94128 const stakeAddress = ( await wallet . getRewardAddresses ( ) ) [ 0 ] ;
95129 if ( ! stakeAddress || ! address ) {
96130 console . error ( "No stake address or payment address found" ) ;
97131 return ;
98132 }
99- const nostrKey = generateNsec ( ) ;
100- createUser ( {
101- address,
102- stakeAddress,
103- nostrKey : JSON . stringify ( nostrKey ) ,
104- } ) ;
133+
134+ // 3) Get DRep key hash (optional)
135+ let drepKeyHash = "" ;
136+ try {
137+ const dRepKey = await wallet . getDRep ( ) ;
138+ console . log ( "DRep key:" , dRepKey ) ;
139+ if ( dRepKey && dRepKey . publicKeyHash ) {
140+ drepKeyHash = dRepKey . publicKeyHash ;
141+ }
142+ } catch ( error ) {
143+ // DRep key is optional, so we continue without it
144+ console . log ( "No DRep key available for this wallet" ) ;
145+ }
146+
147+ // 4) Create or update user (upsert pattern handles both cases)
148+ if ( ! isLoading ) {
149+ const nostrKey = generateNsec ( ) ;
150+ createUser ( {
151+ address,
152+ stakeAddress,
153+ drepKeyHash,
154+ nostrKey : JSON . stringify ( nostrKey ) ,
155+ } ) ;
156+ }
157+ } catch ( error ) {
158+ console . error ( "Error in wallet initialization effect:" , error ) ;
159+
160+ // If we get an "account changed" error, reload the page
161+ if ( error instanceof Error && error . message . includes ( "account changed" ) ) {
162+ console . log ( "Account changed detected, reloading page..." ) ;
163+ window . location . reload ( ) ;
164+ return ;
165+ }
166+
167+ // For other errors, don't throw to prevent app crash
168+ // The user can retry by reconnecting their wallet
105169 }
106170 } ) ( ) ;
107- } , [
108- connected ,
109- wallet ,
110- user ,
111- isLoading ,
112- createUser ,
113- generateNsec ,
114- setUserAddress ,
115- ] ) ;
171+ } , [ connected , wallet , user , isLoading , createUser , generateNsec , setUserAddress ] ) ;
116172
117173 const isWalletPath = router . pathname . includes ( "/wallets/[wallet]" ) ;
118174 const walletPageRoute = router . pathname . split ( "/wallets/[wallet]/" ) [ 1 ] ;
@@ -125,12 +181,18 @@ export default function RootLayout({
125181 { isLoading && < Loading /> }
126182
127183 { /* Sidebar for larger screens */ }
128- < aside className = "hidden border-r border-gray-200/30 dark:border-white/[0.03] bg-muted/40 md:block" >
184+ < aside className = "hidden border-r border-gray-200/30 bg-muted/40 dark:border-white/[0.03] md:block" >
129185 < div className = "flex h-full max-h-screen flex-col" >
130- < header className = "flex h-14 items-center border-b border-gray-200/30 dark:border-white/[0.03] px-4 lg:h-16 lg:px-6" id = "logo-header" data-header = "sidebar" >
186+ < header
187+ className = "flex h-14 items-center border-b border-gray-200/30 px-4 dark:border-white/[0.03] lg:h-16 lg:px-6"
188+ id = "logo-header"
189+ data-header = "sidebar"
190+ >
131191 < Link href = "/" className = "flex items-center gap-3" >
132192 < Logo />
133- < span className = "font-medium text-sm md:text-base lg:text-lg tracking-[-0.01em] select-none" > Multi-Sig Platform</ span >
193+ < span className = "select-none text-sm font-medium tracking-[-0.01em] md:text-base lg:text-lg" >
194+ Multi-Sig Platform
195+ </ span >
134196 </ Link >
135197 </ header >
136198 < nav className = "flex-1 pt-2" >
@@ -143,27 +205,35 @@ export default function RootLayout({
143205
144206 { /* Main content area */ }
145207 < div className = "flex h-screen flex-col" >
146- < header className = "pointer-events-auto relative z-10 border-b border-gray-200/30 dark:border-white/[0.03] bg-muted/40 px-4 lg:px-6" data-header = "main" >
208+ < header
209+ className = "pointer-events-auto relative z-10 border-b border-gray-200/30 bg-muted/40 px-4 dark:border-white/[0.03] lg:px-6"
210+ data-header = "main"
211+ >
147212 < div className = "flex h-14 items-center gap-4 lg:h-16" >
148213 { /* Mobile menu button */ }
149214 < MobileNavigation isWalletPath = { isWalletPath } />
150-
215+
151216 { /* Logo in mobile header - centered */ }
152- < div className = "flex-1 flex justify-center md:hidden" >
153- < Link href = "/" className = "flex items-center gap-2 px-2 py-1 rounded-md hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors" >
217+ < div className = "flex flex-1 justify-center md:hidden" >
218+ < Link
219+ href = "/"
220+ className = "flex items-center gap-2 rounded-md px-2 py-1 transition-colors hover:bg-gray-100/50 dark:hover:bg-gray-800/50"
221+ >
154222 < svg
155- className = "h-7 w-7 text-foreground flex-shrink-0"
223+ className = "h-7 w-7 flex-shrink-0 text-foreground "
156224 enableBackground = "new 0 0 300 200"
157225 viewBox = "0 0 300 200"
158226 xmlns = "http://www.w3.org/2000/svg"
159227 fill = "currentColor"
160228 >
161229 < path d = "m289 127-45-60-45-60c-.9-1.3-2.4-2-4-2s-3.1.7-4 2l-37 49.3c-2 2.7-6 2.7-8 0l-37-49.3c-.9-1.3-2.4-2-4-2s-3.1.7-4 2l-45 60-45 60c-1.3 1.8-1.3 4.2 0 6l45 60c.9 1.3 2.4 2 4 2s3.1-.7 4-2l37-49.3c2-2.7 6-2.7 8 0l37 49.3c.9 1.3 2.4 2 4 2s3.1-.7 4-2l37-49.3c2-2.7 6-2.7 8 0l37 49.3c.9 1.3 2.4 2 4 2s3.1-.7 4-2l45-60c1.3-1.8 1.3-4.2 0-6zm-90-103.3 32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0l-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0zm-90 0 32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0l-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0zm-53 152.6-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0zm90 0-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0zm90 0-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0z" />
162230 </ svg >
163- < span className = "text-base font-medium text-foreground whitespace-nowrap" > Multi-Sig Platform</ span >
231+ < span className = "whitespace-nowrap text-base font-medium text-foreground" >
232+ Multi-Sig Platform
233+ </ span >
164234 </ Link >
165235 </ div >
166-
236+
167237 { /* Wallet selection + breadcrumb row on desktop */ }
168238 { isLoggedIn && (
169239 < div className = "hidden md:block" >
@@ -210,7 +280,7 @@ export default function RootLayout({
210280 ) : (
211281 < >
212282 { /* Desktop buttons */ }
213- < div className = "hidden md:flex items-center space-x-2" >
283+ < div className = "hidden items-center space-x-2 md:flex " >
214284 < WalletDataLoaderWrapper mode = "button" />
215285 < DialogReportWrapper mode = "button" />
216286 < UserDropDownWrapper mode = "button" />
@@ -220,7 +290,7 @@ export default function RootLayout({
220290 < WalletDataLoaderWrapper mode = "menu-item" />
221291 < DialogReportWrapper mode = "menu-item" />
222292 < UserDropDownWrapper mode = "menu-item" />
223- < div className = "h-px bg-border - mx-2 my-1" />
293+ < div className = "- mx-2 my-1 h-px bg-border " />
224294 < LogoutWrapper mode = "menu-item" />
225295 </ MobileActionsMenu >
226296 </ >
@@ -230,7 +300,22 @@ export default function RootLayout({
230300 </ header >
231301
232302 < main className = "relative flex flex-1 flex-col gap-4 overflow-y-auto overflow-x-hidden p-4 md:p-8" >
233- { pageIsPublic || userAddress ? children : < PageHomepage /> }
303+ < WalletErrorBoundary
304+ fallback = {
305+ < div className = "flex flex-col items-center justify-center h-full" >
306+ < h2 className = "text-xl font-semibold mb-2" > Something went wrong</ h2 >
307+ < p className = "text-gray-600 mb-4" > Please try refreshing the page or reconnecting your wallet.</ p >
308+ < button
309+ onClick = { ( ) => window . location . reload ( ) }
310+ className = "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
311+ >
312+ Refresh Page
313+ </ button >
314+ </ div >
315+ }
316+ >
317+ { pageIsPublic || userAddress ? children : < PageHomepage /> }
318+ </ WalletErrorBoundary >
234319 </ main >
235320 </ div >
236321 </ div >
0 commit comments