chore: sync submodule state (parent ref update)
Made-with: Cursor
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user