Skip to content

Commit 8149b9f

Browse files
committed
feat: wallet disconnect confirmation modal (#487)
1 parent 3d2d7de commit 8149b9f

2 files changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import Modal from '@/components/ui/Modal';
5+
6+
interface DisconnectModalProps {
7+
open: boolean;
8+
onClose: () => void;
9+
onConfirm: () => Promise<void>;
10+
walletAddress?: string;
11+
}
12+
13+
export default function DisconnectModal({
14+
open,
15+
onClose,
16+
onConfirm,
17+
walletAddress,
18+
}: DisconnectModalProps) {
19+
const [confirming, setConfirming] = useState(false);
20+
21+
const handleConfirm = async () => {
22+
setConfirming(true);
23+
try {
24+
await onConfirm();
25+
} finally {
26+
setConfirming(false);
27+
}
28+
};
29+
30+
const truncatedAddress = walletAddress
31+
? `${walletAddress.slice(0, 8)}...${walletAddress.slice(-6)}`
32+
: '';
33+
34+
return (
35+
<Modal open={open} onClose={onClose} title="Disconnect Wallet">
36+
<div className="space-y-4">
37+
<div className="text-sm text-gray-600 dark:text-gray-400">
38+
<p className="mb-2">
39+
Are you sure you want to disconnect your wallet?
40+
</p>
41+
{truncatedAddress && (
42+
<p className="text-xs text-gray-500 dark:text-gray-500 break-all">
43+
Address: <code className="font-mono">{truncatedAddress}</code>
44+
</p>
45+
)}
46+
<p className="mt-3 text-sm font-medium text-amber-600 dark:text-amber-500">
47+
You'll need to reconnect to continue using the app.
48+
</p>
49+
</div>
50+
51+
<div className="flex gap-3 pt-2">
52+
<button
53+
onClick={onClose}
54+
disabled={confirming}
55+
className="flex-1 px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors disabled:opacity-50 text-sm font-medium"
56+
>
57+
Cancel
58+
</button>
59+
<button
60+
onClick={handleConfirm}
61+
disabled={confirming}
62+
className="flex-1 px-4 py-2 rounded-lg bg-red-600 hover:bg-red-700 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium flex items-center justify-center gap-2"
63+
>
64+
{confirming && (
65+
<svg
66+
className="animate-spin h-4 w-4"
67+
xmlns="http://www.w3.org/2000/svg"
68+
fill="none"
69+
viewBox="0 0 24 24"
70+
aria-hidden="true"
71+
>
72+
<circle
73+
className="opacity-25"
74+
cx="12"
75+
cy="12"
76+
r="10"
77+
stroke="currentColor"
78+
strokeWidth="4"
79+
/>
80+
<path
81+
className="opacity-75"
82+
fill="currentColor"
83+
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"
84+
/>
85+
</svg>
86+
)}
87+
Disconnect
88+
</button>
89+
</div>
90+
</div>
91+
</Modal>
92+
);
93+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use client';
2+
3+
import { useRouter } from 'next/navigation';
4+
import { useState } from 'react';
5+
import { useWallet } from '@/hooks/useWallet';
6+
import DisconnectModal from '@/components/wallet/DisconnectModal';
7+
8+
export default function WalletMenu() {
9+
const router = useRouter();
10+
const { address, disconnect } = useWallet();
11+
const [open, setOpen] = useState(false);
12+
const [showDisconnectModal, setShowDisconnectModal] = useState(false);
13+
14+
if (!address) return null;
15+
16+
const truncatedAddress = `${address.slice(0, 8)}...${address.slice(-6)}`;
17+
18+
const handleDisconnect = async () => {
19+
await disconnect();
20+
setShowDisconnectModal(false);
21+
router.push('/');
22+
};
23+
24+
return (
25+
<>
26+
<div className="relative">
27+
<button
28+
onClick={() => setOpen(!open)}
29+
className="px-3 py-1.5 text-sm font-medium rounded-lg bg-gray-700 hover:bg-gray-600 text-gray-100 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
30+
>
31+
{truncatedAddress}
32+
</button>
33+
34+
{open && (
35+
<>
36+
<div
37+
className="fixed inset-0 z-10"
38+
onClick={() => setOpen(false)}
39+
aria-hidden="true"
40+
/>
41+
<div className="absolute right-0 mt-2 w-48 bg-gray-800 border border-gray-700 rounded-lg shadow-lg z-20 overflow-hidden">
42+
<button
43+
onClick={() => {
44+
const explorerUrl =
45+
process.env.NEXT_PUBLIC_STELLAR_NETWORK === 'mainnet'
46+
? `https://stellar.expert/explorer/public/account/${address}`
47+
: `https://stellar.expert/explorer/testnet/account/${address}`;
48+
window.open(explorerUrl, '_blank');
49+
setOpen(false);
50+
}}
51+
className="w-full text-left px-4 py-2.5 text-sm text-gray-200 hover:bg-gray-700 transition-colors flex items-center gap-2"
52+
>
53+
<svg
54+
xmlns="http://www.w3.org/2000/svg"
55+
className="h-4 w-4"
56+
viewBox="0 0 20 20"
57+
fill="currentColor"
58+
aria-hidden="true"
59+
>
60+
<path d="M11 3a1 1 0 10-2 0v1a1 1 0 102 0V3zM15.657 5.343a1 1 0 00-1.414-1.414l-.707.707a1 1 0 001.414 1.414l.707-.707zM18 10a1 1 0 01-1 1h-1a1 1 0 110-2h1a1 1 0 011 1zM15.657 14.657a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414l.707.707zM11 17a1 1 0 102 0v-1a1 1 0 10-2 0v1zM5.343 15.657a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414l-.707.707zM2 10a1 1 0 011-1h1a1 1 0 110 2H3a1 1 0 01-1-1zM5.343 5.343a1 1 0 01-1.414 1.414l-.707-.707A1 1 0 014.636 4.636l.707.707zM10 6a4 4 0 100 8 4 4 0 000-8zm0 2a2 2 0 110 4 2 2 0 010-4z" />
61+
</svg>
62+
View on Explorer
63+
</button>
64+
<div className="border-t border-gray-700" />
65+
<button
66+
onClick={() => {
67+
setShowDisconnectModal(true);
68+
setOpen(false);
69+
}}
70+
className="w-full text-left px-4 py-2.5 text-sm text-red-400 hover:bg-red-950/40 transition-colors flex items-center gap-2"
71+
>
72+
<svg
73+
xmlns="http://www.w3.org/2000/svg"
74+
className="h-4 w-4"
75+
viewBox="0 0 20 20"
76+
fill="currentColor"
77+
aria-hidden="true"
78+
>
79+
<path
80+
fillRule="evenodd"
81+
d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414-1.414L13.414 10l1.293-1.293a1 1 0 00-1.414-1.414L12 8.586l-1.293-1.293a1 1 0 00-1.414 1.414L10.586 10l-1.293 1.293a1 1 0 001.414 1.414L12 11.414l1.293 1.293z"
82+
clipRule="evenodd"
83+
/>
84+
</svg>
85+
Disconnect
86+
</button>
87+
</div>
88+
</>
89+
)}
90+
</div>
91+
92+
<DisconnectModal
93+
open={showDisconnectModal}
94+
onClose={() => setShowDisconnectModal(false)}
95+
onConfirm={handleDisconnect}
96+
walletAddress={address}
97+
/>
98+
</>
99+
);
100+
}

0 commit comments

Comments
 (0)