chore: sync submodule state (parent ref update)

Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-02 12:14:09 -08:00
parent 50ab378da9
commit 5efe36b1e0
1100 changed files with 155024 additions and 8674 deletions

View File

@@ -1,15 +1,24 @@
import { useAccount, useConnect, useDisconnect, useChainId, useSwitchChain } from 'wagmi'
import { useEffect, useState } from 'react'
import { useEffect, useState, useRef } from 'react'
const CHAIN_138_ID = 138
const EXPLORER_URL = 'https://explorer.d-bis.org'
export default function WalletConnect() {
interface WalletConnectProps {
/** Callback before disconnect so we can treat it as user-initiated (no "disconnected" toast). */
onBeforeDisconnect?: () => void
}
export default function WalletConnect({ onBeforeDisconnect }: WalletConnectProps) {
const { address, isConnected } = useAccount()
const { connect, connectors, isPending } = useConnect()
const { disconnect } = useDisconnect()
const chainId = useChainId()
const { switchChain, isPending: isSwitching } = useSwitchChain()
const [showChainWarning, setShowChainWarning] = useState(false)
const [showConnectModal, setShowConnectModal] = useState(false)
const [showDropdown, setShowDropdown] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (isConnected && chainId !== CHAIN_138_ID) {
@@ -19,6 +28,16 @@ export default function WalletConnect() {
}
}, [isConnected, chainId])
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setShowDropdown(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
const handleSwitchChain = async () => {
try {
await switchChain({ chainId: CHAIN_138_ID })
@@ -27,14 +46,31 @@ export default function WalletConnect() {
}
}
const handleConnect = (connector: (typeof connectors)[0]) => {
connect({ connector })
setShowConnectModal(false)
}
const copyAddress = () => {
if (address) {
navigator.clipboard.writeText(address)
setShowDropdown(false)
}
}
const viewOnExplorer = () => {
if (address) window.open(`${EXPLORER_URL}/address/${address}`, '_blank', 'noopener')
setShowDropdown(false)
}
if (isConnected) {
return (
<div className="flex items-center gap-3">
<div className="flex items-center gap-3 flex-shrink-0">
{showChainWarning && (
<button
onClick={handleSwitchChain}
disabled={isSwitching}
className="px-4 py-2 text-sm font-bold bg-gradient-to-r from-yellow-500 to-orange-500 text-white rounded-xl hover:from-yellow-600 hover:to-orange-600 disabled:opacity-50 transition-all duration-300 shadow-lg hover:shadow-xl flex items-center gap-2 border border-white/20"
className="px-4 py-2 text-sm font-medium bg-amber-500/20 text-amber-200 rounded-lg hover:bg-amber-500/30 border border-amber-500/40 disabled:opacity-50 transition-colors flex items-center gap-2"
>
{isSwitching ? (
<>
@@ -45,54 +81,85 @@ export default function WalletConnect() {
Switching...
</>
) : (
<>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
Switch to Chain 138
</>
'Switch to Chain 138'
)}
</button>
)}
<div className="flex items-center gap-2 px-4 py-2.5 bg-white/10 backdrop-blur-xl rounded-xl border border-white/20 shadow-lg">
<div className="h-2.5 w-2.5 bg-emerald-400 rounded-full animate-pulse shadow-lg shadow-emerald-400/50"></div>
<span className="text-sm font-bold text-white font-mono">
{address?.slice(0, 6)}...{address?.slice(-4)}
</span>
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setShowDropdown(!showDropdown)}
className="flex items-center gap-2 px-4 py-2.5 bg-[#1a1d24] rounded-xl border border-white/20 hover:border-white/30 transition-colors min-h-[44px] w-full md:w-auto"
aria-expanded={showDropdown}
aria-haspopup="true"
>
<div className="h-2 w-2 bg-emerald-400 rounded-full" />
<span className="text-sm font-medium text-white font-mono">
{address?.slice(0, 6)}...{address?.slice(-4)}
</span>
<svg className={`w-4 h-4 text-[#A0A0A0] transition-transform ${showDropdown ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{showDropdown && (
<div className="absolute right-0 mt-1 py-1 w-48 bg-[#252830] rounded-xl border border-white/10 shadow-xl z-50">
<button onClick={copyAddress} className="w-full px-4 py-2.5 text-left text-sm text-white hover:bg-white/5 transition-colors">
Copy address
</button>
<button onClick={viewOnExplorer} className="w-full px-4 py-2.5 text-left text-sm text-white hover:bg-white/5 transition-colors">
View on Explorer
</button>
<button
onClick={() => { onBeforeDisconnect?.(); disconnect(); setShowDropdown(false) }}
className="w-full px-4 py-2.5 text-left text-sm text-red-400 hover:bg-white/5 transition-colors"
>
Disconnect
</button>
</div>
)}
</div>
<button
onClick={() => disconnect()}
className="px-5 py-2.5 bg-gradient-to-r from-red-500 to-red-600 text-white rounded-xl hover:from-red-600 hover:to-red-700 font-bold transition-all duration-300 shadow-lg hover:shadow-xl transform hover:scale-105 border border-white/20"
>
Disconnect
</button>
</div>
)
}
return (
<div className="flex items-center gap-2">
{connectors.map((connector) => (
<button
key={connector.uid}
onClick={() => connect({ connector })}
disabled={isPending}
className="px-6 py-3 bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 text-white rounded-xl hover:from-blue-600 hover:via-purple-600 hover:to-pink-600 font-bold disabled:opacity-50 transition-all duration-300 shadow-2xl hover:shadow-purple-500/50 transform hover:scale-105 disabled:transform-none border border-white/20"
>
{isPending ? (
<span className="flex items-center gap-2">
<svg className="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Connecting...
</span>
) : (
`Connect ${connector.name}`
)}
</button>
))}
</div>
<>
<button
onClick={() => setShowConnectModal(true)}
className="px-6 py-3 bg-teal-600 text-white font-medium rounded-xl hover:bg-teal-500 transition-colors focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-[#252830] min-h-[44px] w-full md:w-auto"
>
Connect Wallet
</button>
{showConnectModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60" onClick={() => setShowConnectModal(false)}>
<div className="bg-[#252830] rounded-2xl border border-white/10 shadow-2xl w-full max-w-md p-6" onClick={e => e.stopPropagation()}>
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-semibold text-white">Connect Wallet</h2>
<button onClick={() => setShowConnectModal(false)} className="p-2 text-[#A0A0A0] hover:text-white rounded-lg hover:bg-white/5 transition-colors" aria-label="Close">
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
<div className="space-y-2">
{connectors.map((connector) => (
<button
key={connector.uid}
onClick={() => handleConnect(connector)}
disabled={isPending}
className="w-full px-4 py-3 rounded-xl bg-[#1a1d24] border border-white/10 text-white font-medium hover:bg-white/5 hover:border-white/20 disabled:opacity-50 transition-colors text-left flex items-center justify-between"
>
<span>{connector.name}</span>
{isPending && (
<svg className="animate-spin h-5 w-5 text-teal-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
)}
</button>
))}
</div>
</div>
</div>
)}
</>
)
}

View File

@@ -0,0 +1,29 @@
import { useAccount } from 'wagmi'
import { useEffect, useRef } from 'react'
import toast from 'react-hot-toast'
/**
* Shows a toast when the wallet disconnects unexpectedly (e.g. MetaMask extension
* "Lost connection to MetaMask background"). Does not show when the user clicked Disconnect.
*/
export default function WalletDisconnectNotice({
userInitiatedDisconnectRef,
}: {
userInitiatedDisconnectRef: React.MutableRefObject<boolean>
}) {
const { isConnected } = useAccount()
const wasConnectedRef = useRef(false)
useEffect(() => {
if (wasConnectedRef.current && !isConnected) {
if (userInitiatedDisconnectRef.current) {
userInitiatedDisconnectRef.current = false
} else {
toast.error('Wallet disconnected. Please reconnect or reload the page.', { duration: 6000 })
}
}
wasConnectedRef.current = isConnected
}, [isConnected, userInitiatedDisconnectRef])
return null
}