Skip to content

Commit 75c8217

Browse files
feat: add withdraw button and network change disconnection
1 parent 67cb023 commit 75c8217

File tree

8 files changed

+157
-43
lines changed

8 files changed

+157
-43
lines changed

app/page.tsx

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ export default function Home() {
6868
setExpandedCard(expandedCard === cardId ? null : cardId);
6969
};
7070

71+
// Handle network change - disconnect wallet first if connected
72+
const handleNetworkChange = (newNetwork: typeof network) => {
73+
if (isConnected) {
74+
disconnectWallet();
75+
}
76+
switchNetwork(newNetwork);
77+
};
78+
7179
// Fetch claimable balance when connected or token changes
7280
useEffect(() => {
7381
const fetchClaimableBalance = async () => {
@@ -98,7 +106,9 @@ export default function Home() {
98106
};
99107

100108
fetchClaimableBalance();
101-
}, [isConnected, address, selectedToken, getContractIdForToken, coreAPI, setContractBalance]);
109+
// Only re-fetch when connection, address, or token actually changes
110+
// eslint-disable-next-line react-hooks/exhaustive-deps
111+
}, [isConnected, address, selectedToken]);
102112

103113
// Handle token change - clear balance and fetch new one
104114
const handleTokenChange = (newToken: string) => {
@@ -142,6 +152,41 @@ export default function Home() {
142152
setWithdrawAmount('');
143153
};
144154

155+
// Quick withdraw - withdraws full contract balance without modal
156+
const handleQuickWithdraw = async () => {
157+
if (!isConnected || contractBalance <= 0n) {
158+
return;
159+
}
160+
161+
const contractId = getContractIdForToken(selectedToken);
162+
if (!contractId) {
163+
toast.error('Contract not found for token');
164+
return;
165+
}
166+
167+
const contractState = getContractStateForToken(selectedToken);
168+
const tokenUid = contractState?.token_uid || '00';
169+
const withdrawAmount = Number(contractBalance) / 100;
170+
171+
setIsClaiming(true);
172+
toast.info('⏳ Please confirm the withdrawal in your wallet...');
173+
174+
try {
175+
const result = await claimBalance(withdrawAmount, selectedToken, contractId, tokenUid);
176+
177+
// Update balances locally IMMEDIATELY after successful withdrawal
178+
const withdrawAmountCents = Math.round(withdrawAmount * 100);
179+
setBalance(prev => prev + BigInt(withdrawAmountCents));
180+
setContractBalance(0n);
181+
182+
toast.success(`Withdrawal successful! TX: ${result.response.hash?.slice(0, 10)}...`);
183+
} catch (error: any) {
184+
toast.error(error.message || 'Failed to withdraw');
185+
} finally {
186+
setIsClaiming(false);
187+
}
188+
};
189+
145190
const handleWithdrawSubmit = async () => {
146191
if (!isConnected) {
147192
toast.error('Please connect your wallet first');
@@ -253,8 +298,7 @@ export default function Home() {
253298
<TokenSelector selectedToken={selectedToken} onTokenChange={handleTokenChange} />
254299
<NetworkSelector
255300
value={network}
256-
onChange={switchNetwork}
257-
disabled={isConnected}
301+
onChange={handleNetworkChange}
258302
/>
259303
{isConnected ? (
260304
<div className="relative">
@@ -296,7 +340,7 @@ export default function Home() {
296340
selectedToken={selectedToken}
297341
onTokenChange={handleTokenChange}
298342
network={network}
299-
onNetworkChange={switchNetwork}
343+
onNetworkChange={handleNetworkChange}
300344
onConnectWallet={() => setShowWalletModal(true)}
301345
onDisconnectWallet={disconnectWallet}
302346
formattedBalance={formattedBalance}
@@ -314,6 +358,9 @@ export default function Home() {
314358
isConnected={isConnected}
315359
isLoadingBalance={isLoadingBalance}
316360
walletType={walletType ?? undefined}
361+
contractBalance={contractBalance}
362+
onWithdraw={handleQuickWithdraw}
363+
isWithdrawing={isClaiming}
317364
/>
318365

319366
{/* Wallet Connection Modal */}

components/FortuneTigerBetCard.tsx

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default function FortuneTigerBetCard({
6161
onLoadBalance,
6262
walletType,
6363
}: FortuneTigerBetCardProps) {
64-
const { walletBalance, contractBalance, placeBet, connectWallet, balance, refreshBalance, isLoadingBalance, balanceVerified, setBalance, setContractBalance } = useWallet();
64+
const { walletBalance, contractBalance, placeBet, connectWallet, balance, refreshBalance, isLoadingBalance, balanceVerified, setBalance, setContractBalance, claimBalance } = useWallet();
6565
const { isConnected, getContractStateForToken, getContractIdForToken, allBets, address } = useHathor();
6666
const [showMobileDisconnect, setShowMobileDisconnect] = useState(false);
6767
const contractBalanceInTokens = Number(contractBalance) / 100;
@@ -96,6 +96,7 @@ export default function FortuneTigerBetCard({
9696
const [showAnimationSelector, setShowAnimationSelector] = useState(false);
9797
const [debugMode, setDebugMode] = useState(false);
9898
const [hasAttemptedBalance, setHasAttemptedBalance] = useState(false);
99+
const [isWithdrawing, setIsWithdrawing] = useState(false);
99100

100101
const contractState = getContractStateForToken(selectedToken);
101102
const randomBitLength = contractState?.random_bit_length || 16;
@@ -339,6 +340,39 @@ export default function FortuneTigerBetCard({
339340
}
340341
};
341342

343+
const handleWithdrawGains = async () => {
344+
if (!isConnected || contractBalance <= 0n) {
345+
return;
346+
}
347+
348+
const contractId = getContractIdForToken(selectedToken);
349+
if (!contractId) {
350+
toast.error('Contract not found for token');
351+
return;
352+
}
353+
354+
const tokenUid = contractState?.token_uid || '00';
355+
const withdrawAmount = Number(contractBalance) / 100; // Convert cents to tokens
356+
357+
setIsWithdrawing(true);
358+
toast.info('⏳ Please confirm the withdrawal in your wallet...');
359+
360+
try {
361+
const result = await claimBalance(withdrawAmount, selectedToken, contractId, tokenUid);
362+
363+
// Update balances locally IMMEDIATELY after successful withdrawal
364+
const withdrawAmountCents = Math.round(withdrawAmount * 100);
365+
setBalance(prev => prev + BigInt(withdrawAmountCents));
366+
setContractBalance(0n);
367+
368+
toast.success(`Withdrawal successful! TX: ${result.response.hash?.slice(0, 10)}...`);
369+
} catch (error: any) {
370+
toast.error(error.message || 'Failed to withdraw');
371+
} finally {
372+
setIsWithdrawing(false);
373+
}
374+
};
375+
342376
return (
343377
<>
344378
<div className="min-h-screen bg-[#0f0518] flex items-center justify-center p-4 font-serif">
@@ -383,7 +417,6 @@ export default function FortuneTigerBetCard({
383417
<NetworkSelector
384418
value={network}
385419
onChange={onNetworkChange}
386-
disabled={isConnected}
387420
/>
388421
</div>
389422
)}
@@ -664,14 +697,25 @@ export default function FortuneTigerBetCard({
664697
{/* Mobile Balance & Statistics - Below SPIN button on mobile only */}
665698
<div className="flex md:hidden items-stretch justify-center gap-3 mt-2 w-full">
666699
{localFormattedBalance ? (
667-
<div className="flex-1 px-4 py-2 rounded-full border-2 border-yellow-500/60 bg-gradient-to-br from-yellow-900/30 via-black/50 to-yellow-900/30 backdrop-blur-sm flex items-center justify-center">
668-
<div className="flex items-center justify-center gap-2">
669-
<div className="text-base">💰</div>
670-
<span className="text-xs font-bold text-transparent bg-clip-text bg-gradient-to-r from-yellow-200 via-yellow-400 to-yellow-200 font-mono">
671-
{localFormattedBalance}
672-
</span>
700+
<>
701+
<div className="flex-1 px-4 py-2 rounded-full border-2 border-yellow-500/60 bg-gradient-to-br from-yellow-900/30 via-black/50 to-yellow-900/30 backdrop-blur-sm flex items-center justify-center">
702+
<div className="flex items-center justify-center gap-2">
703+
<div className="text-base">💰</div>
704+
<span className="text-xs font-bold text-transparent bg-clip-text bg-gradient-to-r from-yellow-200 via-yellow-400 to-yellow-200 font-mono">
705+
{localFormattedBalance}
706+
</span>
707+
</div>
673708
</div>
674-
</div>
709+
{contractBalance > 0n && (
710+
<button
711+
onClick={handleWithdrawGains}
712+
disabled={isWithdrawing || isPlacingBet || isSpinning}
713+
className="flex-1 px-4 py-2 rounded-full text-xs font-bold transition-all bg-white/10 backdrop-blur-md border-2 border-white/20 shadow-xl text-white/90 hover:text-white hover:bg-white/20 flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed"
714+
>
715+
{isWithdrawing ? 'Withdrawing...' : 'Withdraw gains'}
716+
</button>
717+
)}
718+
</>
675719
) : isLoadingBalance ? (
676720
<div className="flex-1 px-4 py-2 rounded-full text-xs font-medium text-slate-300 text-center flex items-center justify-center">
677721
Authorize to view balance

components/Header.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ export default function Header({ selectedToken, onTokenChange }: HeaderProps) {
2121
setShowModal(true);
2222
};
2323

24+
// Handle network change - disconnect wallet first if connected
25+
const handleNetworkChange = (newNetwork: typeof network) => {
26+
if (isConnected) {
27+
disconnectWallet();
28+
}
29+
switchNetwork(newNetwork);
30+
};
31+
2432
return (
2533
<>
2634
<header className="flex items-center justify-between p-6 border-b border-slate-700">
@@ -34,8 +42,7 @@ export default function Header({ selectedToken, onTokenChange }: HeaderProps) {
3442
<TokenSelector selectedToken={selectedToken} onTokenChange={onTokenChange} />
3543
<NetworkSelector
3644
value={network}
37-
onChange={switchNetwork}
38-
disabled={isConnected}
45+
onChange={handleNetworkChange}
3946
/>
4047
<div className="relative">
4148
<button

components/NetworkSelector.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,26 @@ import { Network } from '@/lib/config';
66
interface NetworkSelectorProps {
77
value: Network;
88
onChange: (network: Network) => void;
9-
disabled?: boolean;
109
}
1110

12-
export function NetworkSelector({ value, onChange, disabled }: NetworkSelectorProps) {
11+
export function NetworkSelector({ value, onChange }: NetworkSelectorProps) {
1312
const handleChange = (value: string) => {
1413
onChange(value as Network);
1514
};
1615

16+
const networkLabels: Record<string, string> = {
17+
testnet: 'Testnet',
18+
mainnet: 'Mainnet',
19+
};
20+
1721
return (
18-
<Select value={value} onValueChange={handleChange} disabled={disabled}>
19-
<SelectTrigger className="w-full md:w-[85px]">
20-
<SelectValue placeholder="Select network" />
22+
<Select value={value} onValueChange={handleChange}>
23+
<SelectTrigger className="w-full md:w-[92px]">
24+
<SelectValue placeholder="Select network" labels={networkLabels} />
2125
</SelectTrigger>
2226
<SelectContent>
23-
<SelectItem value="testnet">India Testnet</SelectItem>
24-
<SelectItem value="mainnet" disabled>
25-
Mainnet (Coming Soon)
26-
</SelectItem>
27+
<SelectItem value="mainnet">Mainnet</SelectItem>
28+
<SelectItem value="testnet">Testnet</SelectItem>
2729
</SelectContent>
2830
</Select>
2931
);

components/UIModeSwitcher.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use client';
22

3-
import { motion } from 'framer-motion';
4-
import { useState, useEffect } from 'react';
3+
import { useState } from 'react';
54
import { toast } from '@/lib/toast';
65
import { WalletType } from '@/types/wallet';
76

@@ -15,25 +14,39 @@ interface UIModeSwitcherProps {
1514
isConnected?: boolean;
1615
isLoadingBalance?: boolean;
1716
walletType?: WalletType;
17+
contractBalance?: bigint;
18+
onWithdraw?: () => Promise<void>;
19+
isWithdrawing?: boolean;
1820
}
1921

20-
export function UIModeSwitcher({ currentMode, onModeChange, balance, onGetBalance, isConnected, isLoadingBalance, walletType }: UIModeSwitcherProps) {
22+
export function UIModeSwitcher({ currentMode, onModeChange, balance, onGetBalance, isConnected, isLoadingBalance, walletType, contractBalance, onWithdraw, isWithdrawing }: UIModeSwitcherProps) {
2123
// If in fortune-tiger mode, show Statistics button with balance or Get Balance button
2224
// Hidden on mobile - mobile version is rendered inside FortuneTigerBetCard
2325
if (currentMode === 'fortune-tiger') {
2426
return (
25-
<div className="hidden md:flex fixed bottom-4 right-4 z-50 items-center gap-3">
27+
<div className="hidden md:flex fixed bottom-4 right-4 z-50 items-center gap-2">
2628
{balance ? (
27-
<div className="px-4 py-2 rounded-full border-2 border-yellow-500/60 bg-gradient-to-br from-yellow-900/30 via-black/50 to-yellow-900/30 backdrop-blur-sm">
28-
<div className="flex items-center gap-2">
29-
<div className="text-lg">💰</div>
30-
<span className="text-sm font-bold text-transparent bg-clip-text bg-gradient-to-r from-yellow-200 via-yellow-400 to-yellow-200 font-mono">
31-
{balance}
32-
</span>
29+
<>
30+
<div className="px-3 py-1.5 rounded-full border-2 border-yellow-500/60 bg-gradient-to-br from-yellow-900/30 via-black/50 to-yellow-900/30 backdrop-blur-sm">
31+
<div className="flex items-center gap-1.5">
32+
<div className="text-sm">💰</div>
33+
<span className="text-xs font-bold text-transparent bg-clip-text bg-gradient-to-r from-yellow-200 via-yellow-400 to-yellow-200 font-mono">
34+
{balance}
35+
</span>
36+
</div>
3337
</div>
34-
</div>
38+
{contractBalance && contractBalance > 0n && onWithdraw && (
39+
<button
40+
onClick={onWithdraw}
41+
disabled={isWithdrawing}
42+
className="px-4 py-1.5 rounded-full text-xs font-bold transition-all bg-white/10 backdrop-blur-md border border-white/20 shadow-xl text-white/90 hover:text-white hover:bg-white/20 disabled:opacity-50 disabled:cursor-not-allowed"
43+
>
44+
{isWithdrawing ? 'Withdrawing...' : 'Withdraw gains'}
45+
</button>
46+
)}
47+
</>
3548
) : isLoadingBalance ? (
36-
<div className="px-6 py-3 rounded-full text-sm font-medium text-slate-300">
49+
<div className="px-4 py-1.5 rounded-full text-xs font-medium text-slate-300">
3750
Authorize to view balance
3851
</div>
3952
) : (
@@ -46,15 +59,15 @@ export function UIModeSwitcher({ currentMode, onModeChange, balance, onGetBalanc
4659
}
4760
onGetBalance();
4861
}}
49-
className="px-6 py-3 rounded-full text-sm font-bold transition-all bg-gradient-to-b from-yellow-400 via-yellow-500 to-yellow-600 text-yellow-900 hover:brightness-110 shadow-xl"
62+
className="px-4 py-1.5 rounded-full text-xs font-bold transition-all bg-gradient-to-b from-yellow-400 via-yellow-500 to-yellow-600 text-yellow-900 hover:brightness-110 shadow-xl"
5063
>
5164
Load Balance
5265
</button>
5366
)
5467
)}
5568
<button
5669
onClick={() => onModeChange('classic')}
57-
className="px-6 py-3 rounded-full text-sm font-bold transition-all bg-white/10 backdrop-blur-md border border-white/20 shadow-xl text-white/90 hover:text-white hover:bg-white/20"
70+
className="px-4 py-1.5 rounded-full text-xs font-bold transition-all bg-white/10 backdrop-blur-md border border-white/20 shadow-xl text-white/90 hover:text-white hover:bg-white/20"
5871
>
5972
Statistics
6073
</button>

components/ui/select.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ const SelectTrigger = React.forwardRef<
5858
})
5959
SelectTrigger.displayName = "SelectTrigger"
6060

61-
const SelectValue = ({ placeholder }: { placeholder?: string }) => {
61+
const SelectValue = ({ placeholder, labels }: { placeholder?: string; labels?: Record<string, string> }) => {
6262
const context = React.useContext(SelectContext)
6363
if (!context) throw new Error('SelectValue must be used within Select')
6464

65-
return <span>{context.value || placeholder}</span>
65+
const displayValue = labels?.[context.value] || context.value || placeholder
66+
return <span>{displayValue}</span>
6667
}
6768

6869
const SelectContent = ({ children, className }: { children: React.ReactNode; className?: string }) => {

contexts/WalletContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ interface WalletContextType {
1414
contractBalance: bigint;
1515
balanceVerified: boolean;
1616
isLoadingBalance: boolean;
17-
setContractBalance: (balance: bigint) => void;
17+
setContractBalance: React.Dispatch<React.SetStateAction<bigint>>;
1818
connectWallet: () => void;
1919
disconnectWallet: () => void;
20-
setBalance: (balance: bigint) => void;
20+
setBalance: React.Dispatch<React.SetStateAction<bigint>>;
2121
placeBet: (betAmount: number, threshold: number, token: string, contractId: string, tokenUid: string, contractBalance: bigint) => Promise<any>;
2222
addLiquidity: (amount: number, token: string, contractId: string, tokenUid: string) => Promise<any>;
2323
removeLiquidity: (amount: number, token: string, contractId: string, tokenUid: string) => Promise<any>;

lib/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function parseContractIds(): string[] {
1010

1111
export const config = {
1212
useMockWallet: process.env.NEXT_PUBLIC_USE_MOCK_WALLET === 'true',
13-
defaultNetwork: (process.env.NEXT_PUBLIC_DEFAULT_NETWORK || 'testnet') as Network,
13+
defaultNetwork: (process.env.NEXT_PUBLIC_DEFAULT_NETWORK || 'mainnet') as Network,
1414
hathorNodeUrls: {
1515
'testnet': process.env.NEXT_PUBLIC_HATHOR_NODE_URL_TESTNET || 'https://node1.india.testnet.hathor.network/v1a',
1616
'mainnet': process.env.NEXT_PUBLIC_HATHOR_NODE_URL_MAINNET || 'https://node1.mainnet.hathor.network/v1a',

0 commit comments

Comments
 (0)