11'use client' ;
22
3- import { useWallet } from '@/contexts/WalletContext' ;
4- import { AlertCircle , Wallet } from 'lucide-react' ;
3+ import { useWallet , StellarNetwork } from '@/contexts/WalletContext' ;
4+ import { AlertCircle , CheckCircle2 , Download , ExternalLink , Globe , RefreshCw , ShieldAlert , Wallet } from 'lucide-react' ;
55import { motion } from 'framer-motion' ;
66import { useEffect , useState } from 'react' ;
77import { usePathname } from 'next/navigation' ;
88
99export default function WalletGate ( { children } : { children : React . ReactNode } ) {
10- const { isConnected, connect, isConnecting, availableWallets, activeWallet } = useWallet ( ) ;
10+ const {
11+ isConnected,
12+ connect,
13+ isConnecting,
14+ availableWallets,
15+ activeWallet,
16+ activeNetwork,
17+ walletNetwork,
18+ isNetworkDivergent,
19+ blockHeight,
20+ switchNetwork,
21+ setAppNetwork,
22+ } = useWallet ( ) ;
23+
1124 const pathname = usePathname ( ) ;
1225 const [ isMounted , setIsMounted ] = useState ( false ) ;
1326
1427 useEffect ( ( ) => {
1528 setIsMounted ( true ) ;
1629 } , [ ] ) ;
1730
18- // Public routes (landing, offline recovery, auth) must always render — on the
19- // server AND the client — so they never depend on mount state or localStorage.
20- // Gated routes stay blocked until mounted to avoid a hydration flicker / a
21- // leaked localStorage wallet bypassing the gate before React hydrates.
2231 const isPublicRoute =
2332 pathname === '/' || pathname === '/offline' || pathname ?. startsWith ( '/auth/' ) ;
2433
@@ -33,57 +42,145 @@ export default function WalletGate({ children }: { children: React.ReactNode })
3342 const hasLocalWallet = typeof window !== 'undefined' && ! ! localStorage . getItem ( 'stellar_wallet' ) ;
3443
3544 if ( isConnected || hasLocalWallet ) {
36- return < > { children } </ > ;
45+ return (
46+ < div className = "relative min-h-screen flex flex-col" >
47+ { isNetworkDivergent && (
48+ < div className = "bg-amber-500/15 border-b border-amber-500/30 p-3 px-4 text-amber-200 flex flex-wrap items-center justify-between gap-3 text-sm z-50" >
49+ < div className = "flex items-center gap-2" >
50+ < ShieldAlert className = "h-5 w-5 text-amber-400 shrink-0" />
51+ < span >
52+ < strong > Network Mismatch Detected:</ strong > Application target is{ ' ' }
53+ < span className = "font-semibold underline" > { activeNetwork } </ span > but connected wallet is on{ ' ' }
54+ < span className = "font-semibold underline" > { walletNetwork } </ span > .
55+ </ span >
56+ </ div >
57+ < div className = "flex items-center gap-2" >
58+ < button
59+ onClick = { ( ) => switchNetwork ( activeNetwork ) }
60+ className = "px-3 py-1 bg-amber-500 text-black font-semibold rounded-md text-xs hover:bg-amber-400 transition-colors"
61+ >
62+ Switch Wallet to { activeNetwork }
63+ </ button >
64+ < button
65+ onClick = { ( ) => setAppNetwork ( ( walletNetwork as StellarNetwork ) || 'TESTNET' ) }
66+ className = "px-3 py-1 bg-neutral-800 text-white font-medium rounded-md text-xs border border-amber-500/30 hover:bg-neutral-700 transition-colors"
67+ >
68+ Align App to { walletNetwork }
69+ </ button >
70+ </ div >
71+ </ div >
72+ ) }
73+ < main className = "flex-1" > { children } </ main >
74+ </ div >
75+ ) ;
3776 }
3877
3978 return (
4079 < div className = "min-h-screen bg-background flex flex-col items-center justify-center p-4" >
41- < motion . div
80+ < motion . div
4281 initial = { { opacity : 0 , y : 20 } }
4382 animate = { { opacity : 1 , y : 0 } }
44- className = "max-w-md w-full bg-bg-secondary border border-border-theme rounded-2xl p-8 shadow-2xl"
83+ className = "max-w-xl w-full bg-bg-secondary border border-border-theme rounded-2xl p-8 shadow-2xl space-y-6 "
4584 >
46- < div className = "flex justify-center mb-6" >
47- < div className = "bg-red-500/10 p-4 rounded-full border border-red-500/20" >
48- < Wallet className = "h-12 w-12 text-red-500" />
85+ < div className = "flex justify-between items-start" >
86+ < div className = "flex items-center gap-3" >
87+ < div className = "bg-red-500/10 p-3 rounded-full border border-red-500/20" >
88+ < Wallet className = "h-8 w-8 text-red-500" />
89+ </ div >
90+ < div >
91+ < h1 className = "text-xl font-black uppercase tracking-tight" >
92+ Connect Web3 Wallet
93+ </ h1 >
94+ < p className = "text-xs text-text-secondary" >
95+ Select your preferred Stellar ecosystem wallet to log in.
96+ </ p >
97+ </ div >
98+ </ div >
99+
100+ < div className = "flex flex-col items-end text-xs" >
101+ < div className = "flex items-center gap-1.5 bg-neutral-800 border border-neutral-700 rounded-full px-3 py-1" >
102+ < Globe className = "h-3.5 w-3.5 text-emerald-400" />
103+ < span className = "font-semibold" > { activeNetwork } </ span >
104+ </ div >
105+ { blockHeight && (
106+ < span className = "text-[10px] text-text-secondary mt-1" >
107+ Block #{ blockHeight . toLocaleString ( ) }
108+ </ span >
109+ ) }
49110 </ div >
50111 </ div >
51-
52- < h1 className = "text-2xl font-black text-center uppercase tracking-tight mb-2" >
53- Authentication Required
54- </ h1 >
55- < p className = "text-text-secondary text-center mb-8" >
56- You must connect your Web3 wallet to access the Web3 Student Lab platform.
57- </ p >
58112
59- < div className = "space-y-4" >
60- { availableWallets . map ( ( wallet ) => (
61- < button
62- key = { wallet . name }
63- onClick = { ( ) => connect ( wallet . name ) }
64- disabled = { isConnecting }
65- className = "w-full flex items-center justify-between p-4 rounded-xl border border-border-theme hover:border-red-500 hover:bg-red-500/5 transition-all group disabled:opacity-50"
66- >
67- < div className = "flex items-center gap-3" >
68- < span className = "text-2xl" > { wallet . icon } </ span >
69- < span className = "font-bold" > { wallet . name } </ span >
113+ < div className = "space-y-3" >
114+ < h2 className = "text-xs font-bold uppercase tracking-wider text-text-secondary" >
115+ Available & Detected Wallets
116+ </ h2 >
117+ { availableWallets . map ( ( wallet ) => {
118+ const installed = wallet . isInstalled ( ) ;
119+ return (
120+ < div
121+ key = { wallet . id }
122+ className = { `flex items-center justify-between p-4 rounded-xl border transition-all ${
123+ installed
124+ ? 'border-border-theme hover:border-red-500 hover:bg-red-500/5 cursor-pointer'
125+ : 'border-neutral-800 bg-neutral-950/40 opacity-75'
126+ } `}
127+ >
128+ < div className = "flex items-center gap-3" >
129+ < span className = "text-2xl" > { wallet . icon } </ span >
130+ < div >
131+ < div className = "flex items-center gap-2" >
132+ < span className = "font-bold text-sm" > { wallet . name } </ span >
133+ { installed ? (
134+ < span className = "text-[10px] bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 px-2 py-0.5 rounded-full flex items-center gap-1" >
135+ < CheckCircle2 className = "h-3 w-3" /> Detected
136+ </ span >
137+ ) : (
138+ < span className = "text-[10px] bg-neutral-800 text-neutral-400 px-2 py-0.5 rounded-full" >
139+ Not Installed
140+ </ span >
141+ ) }
142+ </ div >
143+ < p className = "text-xs text-text-secondary mt-0.5" > { wallet . description } </ p >
144+ </ div >
145+ </ div >
146+
147+ { installed ? (
148+ < button
149+ onClick = { ( ) => connect ( wallet . name ) }
150+ disabled = { isConnecting }
151+ className = "px-4 py-2 bg-red-600 hover:bg-red-500 text-white font-bold text-xs rounded-lg transition-colors disabled:opacity-50 flex items-center gap-1.5"
152+ >
153+ { isConnecting && activeWallet === wallet . name ? (
154+ < >
155+ < RefreshCw className = "h-3.5 w-3.5 animate-spin" />
156+ Connecting...
157+ </ >
158+ ) : (
159+ 'Connect →'
160+ ) }
161+ </ button >
162+ ) : (
163+ < a
164+ href = { wallet . downloadUrl }
165+ target = "_blank"
166+ rel = "noopener noreferrer"
167+ className = "px-3 py-1.5 bg-neutral-800 hover:bg-neutral-700 border border-neutral-700 text-xs font-medium rounded-lg text-neutral-300 transition-colors flex items-center gap-1 shrink-0"
168+ >
169+ < Download className = "h-3.5 w-3.5" />
170+ Install
171+ < ExternalLink className = "h-3 w-3 text-neutral-500" />
172+ </ a >
173+ ) }
70174 </ div >
71- { isConnecting && activeWallet === wallet . name ? (
72- < span className = "text-sm text-text-secondary animate-pulse" > Connecting...</ span >
73- ) : (
74- < span className = "text-sm text-text-secondary opacity-0 group-hover:opacity-100 transition-opacity" >
75- Connect →
76- </ span >
77- ) }
78- </ button >
79- ) ) }
175+ ) ;
176+ } ) }
80177 </ div >
81178
82- < div className = "mt-8 p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-xl flex items-start gap-3" >
83- < AlertCircle className = "h-5 w-5 text-yellow-500 shrink-0 mt-0.5" />
84- < p className = "text-sm text-yellow-200/80" >
85- If you don't have a wallet installed, we recommend installing the Freighter extension for the Stellar network .
86- </ p >
179+ < div className = "p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-xl flex items-start gap-3 text-xs text-yellow-200/80 " >
180+ < AlertCircle className = "h-4 w-4 text-yellow-500 shrink-0 mt-0.5" />
181+ < span >
182+ Targeting < strong > { activeNetwork } </ strong > . If your wallet is on a different network, you will be prompted to switch seamlessly .
183+ </ span >
87184 </ div >
88185 </ motion . div >
89186 </ div >
0 commit comments