Fix: show w3a addr, unifiedWallet across UI, refresh when disconnect w3a

This commit is contained in:
SaraJane
2026-01-15 12:43:19 +00:00
parent 5a1d5791f4
commit c11daa5aff
4 changed files with 82 additions and 29 deletions

View File

@ -1,4 +1,4 @@
import { useWallet } from '@txnlab/use-wallet-react' import { useUnifiedWallet } from './hooks/useUnifiedWallet'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
/** /**
@ -7,7 +7,7 @@ import { Link } from 'react-router-dom'
* Displays features, how it works, and CTAs to connect wallet and create assets * Displays features, how it works, and CTAs to connect wallet and create assets
*/ */
export default function Home() { export default function Home() {
const { activeAddress } = useWallet() const { activeAddress } = useUnifiedWallet()
return ( return (
<div className="bg-white dark:bg-slate-950"> <div className="bg-white dark:bg-slate-950">

View File

@ -1,30 +1,49 @@
import { useWallet } from '@txnlab/use-wallet-react'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useUnifiedWallet } from '../hooks/useUnifiedWallet'
import { ellipseAddress } from '../utils/ellipseAddress' import { ellipseAddress } from '../utils/ellipseAddress'
import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs' import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs'
/** /**
* Account Component * Account Component
* Displays the connected wallet address (shortened) and current network *
* Address links to Lora explorer for easy account tracking * Displays the connected Algorand address (shortened)
* and current network.
*
* Works for BOTH:
* - Web3Auth (Google login)
* - Traditional wallets (Pera / Defly / etc)
*
* Address links to Lora explorer.
*/ */
const Account = () => { const Account = () => {
const { activeAddress } = useWallet() const { activeAddress } = useUnifiedWallet()
const algoConfig = getAlgodConfigFromViteEnvironment() const algoConfig = getAlgodConfigFromViteEnvironment()
// Normalize network name for Lora
const networkName = useMemo(() => { const networkName = useMemo(() => {
return algoConfig.network === '' ? 'localnet' : algoConfig.network.toLocaleLowerCase() return algoConfig.network === '' ? 'localnet' : algoConfig.network.toLowerCase()
}, [algoConfig.network]) }, [algoConfig.network])
// Normalize address to string (VERY IMPORTANT)
const address = typeof activeAddress === 'string' ? activeAddress : activeAddress ? String(activeAddress) : null
if (!address) {
return null
}
const loraUrl = `https://lora.algokit.io/${networkName}/account/${address}/`
return ( return (
<div> <div>
<a <a
className="text-xl text-gray-900 dark:text-slate-100 hover:text-teal-600 dark:hover:text-teal-400 transition" href={loraUrl}
target="_blank" target="_blank"
href={`https://lora.algokit.io/${networkName}/account/${activeAddress}/`} rel="noreferrer"
className="text-xl text-gray-900 dark:text-slate-100 hover:text-teal-600 dark:hover:text-teal-400 transition font-mono"
> >
Address: {ellipseAddress(activeAddress)} Address: {ellipseAddress(address)}
</a> </a>
<div className="text-xl text-gray-900 dark:text-slate-100 mt-2">Network: {networkName}</div> <div className="text-xl text-gray-900 dark:text-slate-100 mt-2">Network: {networkName}</div>
</div> </div>
) )

View File

@ -12,9 +12,7 @@ import { useWeb3Auth } from './Web3AuthProvider'
* Features: * Features:
* - Wallet connection/disconnection with Web3Auth (Google OAuth) * - Wallet connection/disconnection with Web3Auth (Google OAuth)
* - Auto-generation of Algorand wallet from Google credentials * - Auto-generation of Algorand wallet from Google credentials
* - Ellipsized address display for better UX
* - Loading states and error handling * - Loading states and error handling
* - Beautiful Google-style sign-in button
* *
* Usage: * Usage:
* ```tsx * ```tsx
@ -26,6 +24,9 @@ export function Web3AuthButton() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false) const [isDropdownOpen, setIsDropdownOpen] = useState(false)
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
// Lora explorer base (TestNet)
const LORA_ACCOUNT_BASE = 'https://lora.algokit.io/testnet/account'
// Close dropdown when clicking outside // Close dropdown when clicking outside
useEffect(() => { useEffect(() => {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
@ -42,23 +43,11 @@ export function Web3AuthButton() {
// Get address as string safely // Get address as string safely
const getAddressString = (): string => { const getAddressString = (): string => {
if (!algorandAccount?.address) return '' if (!algorandAccount?.address) return ''
// Handle if address is an object (like from algosdk with publicKey property)
if (typeof algorandAccount.address === 'object' && algorandAccount.address !== null) {
// If it has a toString method, use it
if ('toString' in algorandAccount.address && typeof algorandAccount.address === 'function') {
return algorandAccount.address
}
// If it has an addr property (algosdk Account object)
if ('addr' in algorandAccount.address) {
return String(algorandAccount.address)
}
return ''
}
return String(algorandAccount.address) return String(algorandAccount.address)
} }
const getLoraAccountUrl = (address: string) => `${LORA_ACCOUNT_BASE}/${address}`
// Handle login with error feedback // Handle login with error feedback
const handleLogin = async () => { const handleLogin = async () => {
try { try {
@ -135,13 +124,14 @@ export function Web3AuthButton() {
if (algorandAccount && isConnected) { if (algorandAccount && isConnected) {
const address = getAddressString() const address = getAddressString()
const firstLetter = address ? address[0].toUpperCase() : 'A' const firstLetter = address ? address[0].toUpperCase() : 'A'
const loraUrl = address ? getLoraAccountUrl(address) : '#'
return ( return (
<div className="dropdown dropdown-end" data-dropdown> <div className="dropdown dropdown-end" data-dropdown>
<button <button
onClick={() => setIsDropdownOpen(!isDropdownOpen)} onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className="btn btn-sm btn-ghost gap-2 hover:bg-base-200" className="btn btn-sm btn-ghost gap-2 hover:bg-base-200"
title={`Connected: ${address} ${userInfo?.email}`} title={`Connected: ${address} ${userInfo?.email ?? ''}`}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Profile picture - always show first letter of address */} {/* Profile picture - always show first letter of address */}
@ -156,7 +146,10 @@ export function Web3AuthButton() {
{firstLetter} {firstLetter}
</div> </div>
)} )}
{/* Keep address visible in navbar, but not a link here (button toggles dropdown) */}
<span className="font-mono text-sm font-medium">{address}</span> <span className="font-mono text-sm font-medium">{address}</span>
<svg <svg
className={`w-4 h-4 transition-transform ${isDropdownOpen ? 'rotate-180' : ''}`} className={`w-4 h-4 transition-transform ${isDropdownOpen ? 'rotate-180' : ''}`}
fill="none" fill="none"
@ -176,7 +169,11 @@ export function Web3AuthButton() {
<li className="menu-title px-3 py-2"> <li className="menu-title px-3 py-2">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{userInfo.profileImage ? ( {userInfo.profileImage ? (
<img src={userInfo.profileImage} alt="Profile" className="w-10 h-10 rounded-full object-cover ring-2 ring-primary" /> <img
src={userInfo.profileImage}
alt="Profile"
className="w-10 h-10 rounded-full object-cover ring-2 ring-primary"
/>
) : ( ) : (
<div className="w-10 h-10 rounded-full bg-primary text-primary-content flex items-center justify-center text-lg font-bold"> <div className="w-10 h-10 rounded-full bg-primary text-primary-content flex items-center justify-center text-lg font-bold">
{firstLetter} {firstLetter}
@ -196,8 +193,20 @@ export function Web3AuthButton() {
<li className="menu-title px-3"> <li className="menu-title px-3">
<span className="text-xs uppercase">Algorand Address</span> <span className="text-xs uppercase">Algorand Address</span>
</li> </li>
{/* Clickable address -> Lora */}
<li> <li>
<div className="bg-base-200 rounded-lg p-2 font-mono text-xs break-all cursor-default hover:bg-base-200">{address}</div> <a
href={loraUrl}
target="_blank"
rel="noopener noreferrer"
className="bg-base-200 rounded-lg p-2 font-mono text-xs break-all hover:bg-base-300 transition"
title="View account on Lora explorer"
onClick={() => setIsDropdownOpen(false)}
>
{address}
<span className="ml-2 opacity-70"></span>
</a>
</li> </li>
{/* Copy Address Button */} {/* Copy Address Button */}
@ -217,6 +226,21 @@ export function Web3AuthButton() {
</button> </button>
</li> </li>
{/* Explicit "View on Lora" CTA (extra clarity) */}
<li>
<a
href={loraUrl}
target="_blank"
rel="noopener noreferrer"
className="text-sm gap-2"
title="Open in Lora explorer"
onClick={() => setIsDropdownOpen(false)}
>
<span>View on Lora</span>
<span className="opacity-70"></span>
</a>
</li>
<div className="divider my-1"></div> <div className="divider my-1"></div>
{/* Disconnect Button */} {/* Disconnect Button */}

View File

@ -64,6 +64,7 @@ export function Web3AuthProvider({ children }: { children: ReactNode }) {
console.error('🎯 Failed to fetch user info:', err) console.error('🎯 Failed to fetch user info:', err)
} }
} }
setIsInitialized(true) setIsInitialized(true)
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to initialize Web3Auth' const errorMessage = err instanceof Error ? err.message : 'Failed to initialize Web3Auth'
@ -125,6 +126,7 @@ export function Web3AuthProvider({ children }: { children: ReactNode }) {
setIsConnected(false) setIsConnected(false)
setProvider(null) setProvider(null)
setAlgorandAccount(null) setAlgorandAccount(null)
setUserInfo(null)
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }
@ -137,10 +139,18 @@ export function Web3AuthProvider({ children }: { children: ReactNode }) {
await logoutFromWeb3Auth() await logoutFromWeb3Auth()
// Clear React state
setProvider(null) setProvider(null)
setIsConnected(false) setIsConnected(false)
setAlgorandAccount(null) setAlgorandAccount(null)
setUserInfo(null) setUserInfo(null)
/**
* ✅ Fix A (most reliable for templates):
* Force a full refresh after logout so Web3Auth doesn't get stuck
* in an in-between cached state (e.g. button stuck on "Connecting...").
*/
window.location.reload()
} catch (err) { } catch (err) {
console.error('🎯 LOGOUT: Error:', err) console.error('🎯 LOGOUT: Error:', err)
setError(err instanceof Error ? err.message : 'Logout failed') setError(err instanceof Error ? err.message : 'Logout failed')