/**
* CoinRush Wallet Modals V2 - Ultra Premium
* Psychology-driven UX with gamification elements
* Includes Cryptocurrency Payment Options (Demo Mode)
*/
const { useState, useEffect, useRef, useCallback, useMemo } = React;
// ============================================
// UTILITIES
// ============================================
const formatBalance = (cents) => ((cents || 0) / 100).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const formatCurrency = (value) => {
const num = parseFloat(value) || 0;
return num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
// Bonus tiers for gamification
const BONUS_TIERS = [
{ min: 0, max: 50, bonus: 0, label: 'No bonus' },
{ min: 50, max: 100, bonus: 2, label: '+2% Bonus' },
{ min: 100, max: 250, bonus: 5, label: '+5% Bonus' },
{ min: 250, max: 500, bonus: 8, label: '+8% Bonus' },
{ min: 500, max: Infinity, bonus: 10, label: '+10% Bonus' },
];
const getBonusTier = (amount) => {
return BONUS_TIERS.find(t => amount >= t.min && amount < t.max) || BONUS_TIERS[0];
};
const getNextTier = (amount) => {
const currentIndex = BONUS_TIERS.findIndex(t => amount >= t.min && amount < t.max);
return BONUS_TIERS[currentIndex + 1] || null;
};
// ============================================
// PAYMENT METHODS CONFIGURATION
// ============================================
const PAYMENT_METHODS = {
crypto: [
{
id: 'btc',
name: 'Bitcoin',
symbol: 'BTC',
color: '#F7931A',
network: 'Bitcoin Network',
minDeposit: 0.0001,
confirmations: 3,
estimatedTime: '10-60 min',
address: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
popular: true
},
{
id: 'eth',
name: 'Ethereum',
symbol: 'ETH',
color: '#627EEA',
network: 'ERC-20',
minDeposit: 0.001,
confirmations: 12,
estimatedTime: '2-5 min',
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f8EdE8',
popular: true
},
{
id: 'usdt',
name: 'Tether',
symbol: 'USDT',
color: '#26A17B',
network: 'TRC-20',
networks: ['ERC-20', 'TRC-20', 'BEP-20'],
minDeposit: 10,
confirmations: 1,
estimatedTime: '1-5 min',
address: 'TN8sL5FFvJGT3c8PN7V6PPLZ5WVfYqZU3e',
popular: true
},
{
id: 'usdc',
name: 'USD Coin',
symbol: 'USDC',
color: '#2775CA',
network: 'ERC-20',
networks: ['ERC-20', 'Solana', 'Polygon'],
minDeposit: 10,
confirmations: 12,
estimatedTime: '2-5 min',
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f8EdE8'
},
{
id: 'ltc',
name: 'Litecoin',
symbol: 'LTC',
color: '#BFBBBB',
network: 'Litecoin',
minDeposit: 0.01,
confirmations: 6,
estimatedTime: '5-30 min',
address: 'LcHK6vhPSr8E8KcexNokiEwQi6hh8Zc8XV'
},
{
id: 'sol',
name: 'Solana',
symbol: 'SOL',
color: '#9945FF',
network: 'Solana',
minDeposit: 0.1,
confirmations: 1,
estimatedTime: '< 1 min',
address: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU'
},
{
id: 'doge',
name: 'Dogecoin',
symbol: 'DOGE',
color: '#C2A633',
network: 'Dogecoin',
minDeposit: 50,
confirmations: 6,
estimatedTime: '10-30 min',
address: 'DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L'
},
{
id: 'bnb',
name: 'BNB',
symbol: 'BNB',
color: '#F3BA2F',
network: 'BEP-20',
minDeposit: 0.01,
confirmations: 15,
estimatedTime: '2-5 min',
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f8EdE8'
}
],
fiat: [
{ id: 'card', name: 'Credit/Debit Card', icon: 'card', fee: '2.5%', instant: true },
{ id: 'bank', name: 'Bank Transfer', icon: 'bank', fee: 'Free', instant: false }
]
};
// ============================================
// ICONS - Refined SVGs
// ============================================
const CloseIcon = () => (
);
const CheckIcon = () => (
);
const ShieldCheckIcon = () => (
);
const LockIcon = () => (
);
const ArrowDownIcon = () => (
);
const ArrowUpIcon = () => (
);
const WalletIcon = () => (
);
const SparklesIcon = () => (
);
const InfoIcon = () => (
);
const SpinnerIcon = ({ size = 24 }) => (
);
const GiftIcon = () => (
);
// Cryptocurrency Icons
const BitcoinIcon = ({ size = 24 }) => (
);
const EthereumIcon = ({ size = 24 }) => (
);
const TetherIcon = ({ size = 24 }) => (
);
const UsdcIcon = ({ size = 24 }) => (
);
const LitecoinIcon = ({ size = 24 }) => (
);
const SolanaIcon = ({ size = 24 }) => (
);
const DogeIcon = ({ size = 24 }) => (
);
const BnbIcon = ({ size = 24 }) => (
);
const CardIcon = ({ size = 24 }) => (
);
const BankIcon = ({ size = 24 }) => (
);
const CopyIcon = ({ size = 16 }) => (
);
const QrCodeIcon = ({ size = 20 }) => (
);
// Additional icons for premium UI (replacing emojis)
const ShieldIcon = ({ size = 16 }) => (
);
const ZapIcon = ({ size = 16 }) => (
);
const TrendingUpIcon = ({ size = 16 }) => (
);
const AlertTriangleIcon = ({ size = 14 }) => (
);
const StarIcon = ({ size = 16 }) => (
);
const CelebrationIcon = ({ size = 18 }) => (
);
const CoinsIcon = ({ size = 16 }) => (
);
const ClockIcon = ({ size = 16 }) => (
);
// Get crypto icon component
const getCryptoIcon = (id, size = 24) => {
const icons = {
btc: BitcoinIcon,
eth: EthereumIcon,
usdt: TetherIcon,
usdc: UsdcIcon,
ltc: LitecoinIcon,
sol: SolanaIcon,
doge: DogeIcon,
bnb: BnbIcon
};
const IconComponent = icons[id];
return IconComponent ? : null;
};
// ============================================
// 2FA SETUP COMPONENT
// ============================================
function TwoFactorSetup({ onSetupComplete, onCancel }) {
const [step, setStep] = useState('loading');
const [setupData, setSetupData] = useState(null);
const [confirmCode, setConfirmCode] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const inputRefs = useRef([]);
useEffect(() => {
initSetup();
}, []);
useEffect(() => {
if (step === 'setup' && inputRefs.current[0]) {
inputRefs.current[0].focus();
}
}, [step]);
const initSetup = async () => {
try {
const res = await fetch('/api/2fa/setup', { method: 'POST', credentials: 'include' });
if (!res.ok) throw new Error('Failed to initialize 2FA');
const data = await res.json();
setSetupData(data);
setStep('setup');
} catch (err) {
setError(err.message);
setStep('error');
}
};
const handleConfirm = async () => {
if (confirmCode.length !== 6) {
setError('Enter all 6 digits');
return;
}
setIsLoading(true);
setError('');
try {
const res = await fetch('/api/2fa/confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ code: confirmCode })
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Invalid code. Please try again.');
setStep('success');
setTimeout(() => onSetupComplete(), 1500);
} catch (err) {
setError(err.message || 'Invalid code. Please try again.');
setConfirmCode('');
inputRefs.current[0]?.focus();
} finally {
setIsLoading(false);
}
};
const handleCodeInput = (index, value) => {
if (!/^\d*$/.test(value)) return;
const newCode = confirmCode.split('');
newCode[index] = value;
const updatedCode = newCode.join('').slice(0, 6);
setConfirmCode(updatedCode);
setError('');
if (value && index < 5) inputRefs.current[index + 1]?.focus();
if (updatedCode.length === 6) setTimeout(() => handleConfirm(), 100);
};
const handleKeyDown = (index, e) => {
if (e.key === 'Backspace' && !confirmCode[index] && index > 0) {
inputRefs.current[index - 1]?.focus();
}
};
const handlePaste = (e) => {
e.preventDefault();
const pasted = e.clipboardData.getData('text').replace(/\D/g, '').slice(0, 6);
setConfirmCode(pasted);
if (pasted.length === 6) setTimeout(() => handleConfirm(), 100);
};
if (step === 'loading') {
return (
Setting Up Security
Generating your secure key...
);
}
if (step === 'error') {
return (
✕
Setup Failed
{error}
);
}
if (step === 'success') {
return (
2FA Enabled!
Your account is now protected
);
}
return (
Enable 2FA Protection
Add an extra layer of security
1
Get Google Authenticator
Download from App Store or Play Store
2
Scan QR Code
Tap + in the app, then scan below
{setupData?.qr_code_data_url ? (

) : (
)}
Can't scan? Enter manually
{setupData?.secret}
3
Enter 6-Digit Code
From your authenticator app
{[0, 1, 2, 3, 4, 5].map(i => (
inputRefs.current[i] = el}
type="text"
inputMode="numeric"
maxLength={1}
value={confirmCode[i] || ''}
onChange={e => handleCodeInput(i, e.target.value)}
onKeyDown={e => handleKeyDown(i, e)}
className={`code-input ${confirmCode.length === 6 ? 'success' : (error ? 'error' : '')}`}
disabled={isLoading}
/>
))}
{error && confirmCode.length < 6 &&
{error}
}
);
}
// ============================================
// 2FA VERIFY COMPONENT
// ============================================
function TwoFactorVerify({ onVerify, onCancel, isLoading, amount, type = 'withdraw' }) {
const [code, setCode] = useState('');
const [error, setError] = useState('');
const inputRefs = useRef([]);
useEffect(() => {
inputRefs.current[0]?.focus();
}, []);
const handleCodeInput = (index, value) => {
if (!/^\d*$/.test(value)) return;
const newCode = code.split('');
newCode[index] = value;
const updatedCode = newCode.join('').slice(0, 6);
setCode(updatedCode);
setError('');
if (value && index < 5) inputRefs.current[index + 1]?.focus();
if (updatedCode.length === 6) handleSubmit(updatedCode);
};
const handleKeyDown = (index, e) => {
if (e.key === 'Backspace' && !code[index] && index > 0) {
inputRefs.current[index - 1]?.focus();
}
};
const handlePaste = (e) => {
e.preventDefault();
const pasted = e.clipboardData.getData('text').replace(/\D/g, '').slice(0, 6);
setCode(pasted);
if (pasted.length === 6) handleSubmit(pasted);
};
const handleSubmit = async (codeValue = code) => {
if (codeValue.length !== 6) return;
try {
await onVerify(codeValue);
} catch (err) {
setError(err.message || 'Invalid code');
setCode('');
inputRefs.current[0]?.focus();
}
};
return (
Verify {type === 'deposit' ? 'Deposit' : 'Withdrawal'}
{type === 'deposit' ? 'Depositing' : 'Withdrawing'} ${formatBalance(amount)}
Enter your 6-digit authenticator code
{[0, 1, 2, 3, 4, 5].map(i => (
inputRefs.current[i] = el}
type="text"
inputMode="numeric"
maxLength={1}
value={code[i] || ''}
onChange={e => handleCodeInput(i, e.target.value)}
onKeyDown={e => handleKeyDown(i, e)}
className={`code-input ${error ? 'error' : ''}`}
disabled={isLoading}
/>
))}
{error &&
{error}
}
);
}
// ============================================
// CRYPTO DEPOSIT VIEW COMPONENT
// ============================================
function CryptoDepositView({ crypto, onBack, amount }) {
const [copied, setCopied] = useState(false);
const [selectedNetwork, setSelectedNetwork] = useState(crypto.network);
const handleCopyAddress = () => {
navigator.clipboard.writeText(crypto.address);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
// Generate simple QR code placeholder (in production, use a real QR library)
const generateQRPlaceholder = () => {
return (
QR Code
Scan with your wallet
);
};
return (
{getCryptoIcon(crypto.id, 48)}
Deposit {crypto.name}
Send {crypto.symbol} to the address below
{crypto.networks && (
{crypto.networks.map(net => (
))}
Sending assets via wrong network may result in loss of funds
)}
{generateQRPlaceholder()}
{crypto.address}
Minimum Deposit
{crypto.minDeposit} {crypto.symbol}
Confirmations
{crypto.confirmations} blocks
Estimated Time
{crypto.estimatedTime}
Network Fee
Paid by you
DEMO MODE
This is a demonstration. No real cryptocurrency transactions are processed.
);
}
// ============================================
// DEPOSIT MODAL
// ============================================
function DepositModal({ isOpen, onClose, userBalance, onBalanceUpdate }) {
const [step, setStep] = useState('form');
const [paymentType, setPaymentType] = useState('crypto'); // 'crypto' or 'fiat'
const [selectedCrypto, setSelectedCrypto] = useState(null);
const [pendingCrypto, setPendingCrypto] = useState(null); // Track crypto selection during 2FA flow
const [amount, setAmount] = useState('');
const [twoFaStatus, setTwoFaStatus] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [newBalance, setNewBalance] = useState(0);
const modalRef = useRef(null);
const inputRef = useRef(null);
const QUICK_AMOUNTS = [10, 25, 50, 100, 250, 500];
const amountNum = parseFloat(amount || 0);
const amountInCents = amountNum * 100;
const minDeposit = 100;
const isValidAmount = amountInCents >= minDeposit;
const requires2FA = amountInCents >= 5000;
// Bonus tier calculation
const currentTier = getBonusTier(amountNum);
const nextTier = getNextTier(amountNum);
const bonusAmount = (amountNum * currentTier.bonus / 100).toFixed(2);
const totalWithBonus = (amountNum + parseFloat(bonusAmount)).toFixed(2);
useEffect(() => {
if (isOpen) {
check2FAStatus();
document.body.style.overflow = 'hidden';
setTimeout(() => inputRef.current?.focus(), 100);
} else {
document.body.style.overflow = '';
setStep('form');
setPaymentType('crypto');
setSelectedCrypto(null);
setPendingCrypto(null);
setAmount('');
setError('');
}
return () => { document.body.style.overflow = ''; };
}, [isOpen]);
const check2FAStatus = async () => {
try {
const res = await fetch('/api/2fa/status', { credentials: 'include' });
if (res.ok) setTwoFaStatus(await res.json());
} catch (err) { console.error('2FA check failed:', err); }
};
const handleBackdropClick = (e) => {
if (e.target === modalRef.current) onClose();
};
const handleSelectCrypto = (crypto) => {
if (!isValidAmount) {
setError('Please enter a valid deposit amount first (minimum $1.00)');
return;
}
// For crypto deposits >= $50, require 2FA first
if (requires2FA) {
setPendingCrypto(crypto);
if (!twoFaStatus?.enabled) {
setStep('2fa-setup');
return;
}
setStep('2fa-verify');
return;
}
setSelectedCrypto(crypto);
setStep('crypto-deposit');
};
const handleDeposit = () => {
if (!isValidAmount) {
setError('Minimum deposit is $1.00');
return;
}
if (requires2FA) {
if (!twoFaStatus?.enabled) {
setStep('2fa-setup');
return;
}
setStep('2fa-verify');
return;
}
processDeposit();
};
const processDeposit = async (twoFaCode = null) => {
setStep('processing');
setIsLoading(true);
try {
const res = await fetch('/api/wallet/deposit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ amount: amountInCents, two_factor_code: twoFaCode })
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Deposit failed');
setNewBalance(data.new_balance);
setStep('success');
// Update balance with floating animation (positive delta)
if (window.updateCoinRushBalance) {
window.updateCoinRushBalance(data.new_balance, amountInCents);
}
if (typeof window.refreshUserBalance === 'function') window.refreshUserBalance();
} catch (err) {
setError(err.message);
setStep('error');
} finally {
setIsLoading(false);
}
};
if (!isOpen) return null;
// Handle 2FA verification - for crypto, proceed to crypto-deposit; for fiat, process deposit
const handleTwoFaVerified = (twoFaCode) => {
if (pendingCrypto) {
// Crypto deposit - after 2FA, show the crypto deposit view
setSelectedCrypto(pendingCrypto);
setPendingCrypto(null);
setStep('crypto-deposit');
} else {
// Fiat deposit - process immediately
processDeposit(twoFaCode);
}
};
const renderContent = () => {
switch (step) {
case 'crypto-deposit':
return (
<>
{ setSelectedCrypto(null); setStep('form'); }}
amount={amountNum}
/>
>
);
case '2fa-setup':
return { check2FAStatus(); setStep('2fa-verify'); }} onCancel={() => { setPendingCrypto(null); setStep('form'); }} />;
case '2fa-verify':
return { setPendingCrypto(null); setStep('form'); }} isLoading={isLoading} type="deposit" />;
case 'processing':
return (
Processing Deposit
Adding funds to your account...
);
case 'success':
return (
Deposit Complete!
+${formatCurrency(amount)}
{currentTier.bonus > 0 && (
+${bonusAmount} bonus added!
)}
New balance: ${formatBalance(newBalance)}
);
case 'error':
return (
✕
Deposit Failed
{error}
);
default:
return (
<>
Add Funds
Balance:
${formatBalance(userBalance)}
{/* Payment Method Tabs */}
{/* Crypto Payment Options */}
{paymentType === 'crypto' && (
{/* Inline Amount Input */}
{QUICK_AMOUNTS.map(val => (
))}
{error &&
}
{PAYMENT_METHODS.crypto.map(crypto => (
))}
Secure & Private
Low Fees
Fast Processing
)}
{/* Fiat Payment Options */}
{paymentType === 'fiat' && (
<>
Amount to Deposit
{currentTier.bonus > 0 && (
{currentTier.label}
)}
{QUICK_AMOUNTS.map(amt => {
const tier = getBonusTier(amt);
return (
);
})}
{/* Bonus tier progress */}
{amountNum >= 10 && nextTier && (
Current Bonus
{currentTier.bonus > 0 ? currentTier.label : 'None'}
Deposit ${nextTier.min} to unlock {nextTier.label}
)}
{requires2FA && (
{twoFaStatus?.enabled ? '2FA Verification Required' : '2FA Setup Required'}
{twoFaStatus?.enabled ? 'Confirm with your authenticator app' : 'Deposits over $50 require 2FA protection'}
)}
{error &&
{error}
}
>
)}
Demo Mode Active
This is a demonstration. No real money transactions are processed.
{paymentType === 'fiat' && (
)}
>
);
}
};
return (
);
}
// ============================================
// CRYPTO WITHDRAW VIEW COMPONENT
// ============================================
function CryptoWithdrawView({ crypto, onBack, amount, onConfirm, isLoading }) {
const [walletAddress, setWalletAddress] = useState('');
const [selectedNetwork, setSelectedNetwork] = useState(crypto.network);
const [error, setError] = useState('');
const handleWithdraw = () => {
if (!walletAddress || walletAddress.length < 20) {
setError('Please enter a valid wallet address');
return;
}
onConfirm(walletAddress, selectedNetwork);
};
return (
{getCryptoIcon(crypto.id, 48)}
Withdraw to {crypto.name}
Enter your {crypto.symbol} wallet address
{crypto.networks && (
{crypto.networks.map(net => (
))}
Ensure you select the correct network that matches your receiving wallet
)}
{ setWalletAddress(e.target.value); setError(''); }}
/>
{error &&
{error}
}
Amount
${amount.toFixed(2)} USD
Network Fee
~$2.50 (varies)
You Receive
~${(amount - 2.50).toFixed(2)} in {crypto.symbol}
DEMO MODE
This is a demonstration. No real cryptocurrency will be sent.
);
}
// ============================================
// WITHDRAW MODAL
// ============================================
function WithdrawModal({ isOpen, onClose, userBalance }) {
const [step, setStep] = useState('form');
const [paymentType, setPaymentType] = useState('crypto');
const [selectedCrypto, setSelectedCrypto] = useState(null);
const [pendingCrypto, setPendingCrypto] = useState(null); // Track crypto selection during 2FA flow
const [amount, setAmount] = useState('');
const [twoFaStatus, setTwoFaStatus] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const modalRef = useRef(null);
const inputRef = useRef(null);
const QUICK_AMOUNTS = [10, 25, 50, 100, 250, 500];
const amountNum = parseFloat(amount || 0);
const amountInCents = amountNum * 100;
const minWithdraw = 100;
const maxWithdraw = userBalance || 0;
const isValidAmount = amountInCents >= minWithdraw && amountInCents <= maxWithdraw;
const requires2FA = amountInCents >= 5000; // $50+ requires 2FA for all payment types
useEffect(() => {
if (isOpen) {
check2FAStatus();
document.body.style.overflow = 'hidden';
setTimeout(() => inputRef.current?.focus(), 100);
} else {
document.body.style.overflow = '';
setStep('form');
setPaymentType('crypto');
setSelectedCrypto(null);
setPendingCrypto(null);
setAmount('');
setError('');
}
return () => { document.body.style.overflow = ''; };
}, [isOpen]);
const check2FAStatus = async () => {
try {
const res = await fetch('/api/2fa/status', { credentials: 'include' });
if (res.ok) setTwoFaStatus(await res.json());
} catch (err) { console.error('2FA check failed:', err); }
};
const handleBackdropClick = (e) => {
if (e.target === modalRef.current) onClose();
};
const handleSelectCrypto = (crypto) => {
if (!isValidAmount) {
setError('Please enter a valid withdrawal amount first');
return;
}
// For crypto withdrawals, also check 2FA if amount >= $50
if (requires2FA) {
setPendingCrypto(crypto); // Remember which crypto was selected
if (!twoFaStatus?.enabled) {
setStep('2fa-setup');
return;
}
setStep('2fa-verify');
return;
}
setSelectedCrypto(crypto);
setStep('crypto-withdraw');
};
const handleWithdraw = () => {
if (!isValidAmount) {
setError(amountInCents < minWithdraw ? 'Minimum withdrawal is $1.00' : 'Insufficient balance');
return;
}
if (requires2FA) {
if (!twoFaStatus?.enabled) {
setStep('2fa-setup');
return;
}
setStep('2fa-verify');
return;
}
processWithdrawal();
};
const processWithdrawal = async (twoFaCode = null) => {
setStep('processing');
setIsLoading(true);
try {
const res = await fetch('/api/wallet/withdraw', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ amount: amountInCents, two_factor_code: twoFaCode })
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Withdrawal failed');
setStep('success');
// Update balance with floating animation (negative delta)
const newBalance = data.new_balance !== undefined ? data.new_balance : (userBalance - amountInCents);
if (window.updateCoinRushBalance) {
window.updateCoinRushBalance(newBalance, -amountInCents);
}
if (typeof window.refreshUserBalance === 'function') window.refreshUserBalance();
} catch (err) {
setError(err.message);
setStep('error');
} finally {
setIsLoading(false);
}
};
if (!isOpen) return null;
// Handle 2FA verification - for crypto, proceed to crypto-withdraw; for fiat, process withdrawal
const handleTwoFaVerified = (twoFaCode) => {
if (pendingCrypto) {
// Crypto withdrawal - after 2FA, show the crypto withdraw view
setSelectedCrypto(pendingCrypto);
setPendingCrypto(null);
setStep('crypto-withdraw');
} else {
// Fiat withdrawal - process immediately
processWithdrawal(twoFaCode);
}
};
const renderContent = () => {
switch (step) {
case '2fa-setup':
return { check2FAStatus(); setStep('2fa-verify'); }} onCancel={() => { setPendingCrypto(null); setStep('form'); }} />;
case '2fa-verify':
return { setPendingCrypto(null); setStep('form'); }} isLoading={isLoading} type="withdraw" />;
case 'processing':
return (
Processing Withdrawal
Transferring your funds...
);
case 'success':
return (
Withdrawal Complete!
-${formatCurrency(amount)}
Funds have been transferred successfully
);
case 'error':
return (
✕
Withdrawal Failed
{error}
);
case 'crypto-withdraw':
return (
<>
{ setSelectedCrypto(null); setStep('form'); }}
amount={amountNum}
onConfirm={(address, network) => processWithdrawal()}
isLoading={isLoading}
/>
>
);
default:
return (
<>
Withdraw Funds
Available:
${formatBalance(userBalance)}
{/* Amount Section First */}
{QUICK_AMOUNTS.filter(a => a * 100 <= maxWithdraw).map(amt => (
))}
{/* Payment Method Tabs */}
{/* Crypto Withdrawal Options */}
{paymentType === 'crypto' && (
Select cryptocurrency to receive
{PAYMENT_METHODS.crypto.map(crypto => (
))}
{!isValidAmount && amountNum > 0 && (
{amountInCents < minWithdraw ? 'Minimum withdrawal is $1.00' : 'Insufficient balance'}
)}
)}
{/* Fiat Withdrawal */}
{paymentType === 'fiat' && (
Bank transfers are not yet available. Please use cryptocurrency for withdrawals.
)}
{requires2FA && (
{twoFaStatus?.enabled ? '2FA Verification Required' : '2FA Setup Required'}
{twoFaStatus?.enabled ? 'Confirm with your authenticator app' : 'Withdrawals over $50 require 2FA protection'}
)}
{error &&
{error}
}
Demo Mode
This is a demonstration. No real money or crypto is being transferred.
>
);
}
};
return (
);
}
// ============================================
// GLOBAL MODAL MANAGER
// ============================================
function WalletModalsManager() {
const [depositOpen, setDepositOpen] = useState(false);
const [withdrawOpen, setWithdrawOpen] = useState(false);
const [userBalance, setUserBalance] = useState(0);
useEffect(() => {
window.openDepositModal = () => { fetchBalance(); setDepositOpen(true); };
window.openWithdrawModal = () => { fetchBalance(); setWithdrawOpen(true); };
return () => { window.openDepositModal = null; window.openWithdrawModal = null; };
}, []);
const fetchBalance = async () => {
try {
const res = await fetch('/me', { credentials: 'include' });
if (res.ok) {
const data = await res.json();
setUserBalance(data.balance || 0);
}
} catch (err) { console.error('Balance fetch failed:', err); }
};
return (
<>
setDepositOpen(false)} userBalance={userBalance} />
setWithdrawOpen(false)} userBalance={userBalance} />
>
);
}
// Auto-mount
document.addEventListener('DOMContentLoaded', () => {
let container = document.getElementById('wallet-modals-root');
if (!container) {
container = document.createElement('div');
container.id = 'wallet-modals-root';
document.body.appendChild(container);
}
ReactDOM.render(React.createElement(WalletModalsManager), container);
});
// Mount if ready
if (document.readyState !== 'loading') {
setTimeout(() => {
let container = document.getElementById('wallet-modals-root');
if (!container) {
container = document.createElement('div');
container.id = 'wallet-modals-root';
document.body.appendChild(container);
}
if (!container.hasChildNodes()) {
ReactDOM.render(React.createElement(WalletModalsManager), container);
}
}, 100);
}
// Exports
window.DepositModal = DepositModal;
window.WithdrawModal = WithdrawModal;
window.openDepositModal = window.openDepositModal || (() => {});
window.openWithdrawModal = window.openWithdrawModal || (() => {});