usePurchaseV5.ts
1 import { useState, useEffect } from 'react' 2 import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi' 3 import { KARAOKE_CONTRACT_V5_ADDRESS } from '../constants/contracts' 4 import { KARAOKE_SCHOOL_V5_ABI } from '../contracts/abis/KaraokeSchoolV5' 5 import { toast } from 'sonner' 6 import { useTranslation } from 'react-i18next' 7 8 const CREDIT_PACK_PRICE = 290000000000000n // 0.00029 ETH 9 10 export function usePurchaseV5() { 11 const { address, isConnected } = useAccount() 12 const [isPurchasing, setIsPurchasing] = useState(false) 13 const [lastPurchase, setLastPurchase] = useState<{ type: 'credits', timestamp: number } | null>(null) 14 const { t } = useTranslation() 15 16 // Use US as default country for V5 17 const defaultCountry = 'US' 18 19 // Read credits 20 const { data: credits, refetch: refetchCredits, isLoading: isLoadingCredits } = useReadContract({ 21 address: KARAOKE_CONTRACT_V5_ADDRESS, 22 abi: KARAOKE_SCHOOL_V5_ABI, 23 functionName: 'credits', 24 args: address ? [address] : undefined, 25 query: { 26 enabled: !!address, 27 }, 28 }) 29 30 // Debug logging 31 useEffect(() => { 32 console.log('🔍 V5 Credits Debug:', { 33 address, 34 contractAddress: KARAOKE_CONTRACT_V5_ADDRESS, 35 credits: credits?.toString(), 36 isLoading: isLoadingCredits 37 }) 38 }, [address, credits, isLoadingCredits]) 39 40 // Write contract 41 const { writeContract: buyCredits, data: buyHash, error: writeError } = useWriteContract() 42 43 // Wait for transaction 44 const { isSuccess: isBuySuccess, isLoading: isWaitingForTx } = useWaitForTransactionReceipt({ hash: buyHash }) 45 46 // Handle purchase success 47 useEffect(() => { 48 if (isBuySuccess) { 49 console.log('✅ Purchase successful!', { 50 txHash: buyHash, 51 timestamp: new Date().toISOString() 52 }) 53 54 setIsPurchasing(false) 55 setLastPurchase({ type: 'credits', timestamp: Date.now() }) 56 toast.success(t('pricing.purchaseSuccess', 'Purchase successful!')) 57 58 // Add delay before refetching to ensure blockchain state is updated 59 setTimeout(() => { 60 console.log('🔄 Refetching balance...') 61 refetchCredits() 62 }, 2000) 63 } 64 }, [isBuySuccess, buyHash, refetchCredits, t]) 65 66 // Handle transaction errors 67 useEffect(() => { 68 if (writeError && isPurchasing) { 69 console.error('Purchase error:', writeError) 70 setIsPurchasing(false) 71 72 const errorMessage = writeError.message || 'Purchase failed' 73 74 // Handle specific error types 75 if (errorMessage.includes('User rejected') || errorMessage.includes('user rejected')) { 76 toast.error(t('errors.wallet.transactionCancelled'), { 77 description: t('errors.wallet.transactionCancelledDesc') 78 }) 79 } else if (errorMessage.includes('insufficient funds')) { 80 toast.error(t('errors.wallet.insufficientFunds', 'Insufficient funds'), { 81 description: t('errors.wallet.insufficientFundsDesc', 'Add ETH to continue') 82 }) 83 } else { 84 toast.error(t('errors.network.requestFailed'), { 85 description: t('errors.network.requestFailedDesc') 86 }) 87 } 88 } 89 }, [writeError, isPurchasing, t]) 90 91 // Handle transaction timeout 92 useEffect(() => { 93 if (isWaitingForTx && isPurchasing) { 94 const timeout = setTimeout(() => { 95 if (isPurchasing) { 96 toast.error(t('errors.generation.requestTimeout'), { 97 description: t('errors.generation.requestTimeoutDesc') 98 }) 99 } 100 }, 30000) // 30 second timeout 101 102 return () => clearTimeout(timeout) 103 } 104 }, [isWaitingForTx, isPurchasing, t]) 105 106 const handleBuyCredits = async () => { 107 if (!address) { 108 toast.error(t('errors.wallet.walletNotConnected'), { 109 description: t('errors.wallet.walletNotConnectedDesc') 110 }) 111 return 112 } 113 114 console.log('💳 Purchasing credits with ETH...') 115 console.log('Contract:', KARAOKE_CONTRACT_V5_ADDRESS) 116 console.log('Price:', CREDIT_PACK_PRICE) 117 console.log('Country:', defaultCountry) 118 119 setIsPurchasing(true) 120 try { 121 await buyCredits({ 122 address: KARAOKE_CONTRACT_V5_ADDRESS, 123 abi: KARAOKE_SCHOOL_V5_ABI, 124 functionName: 'buyCredits', 125 args: [defaultCountry], 126 value: CREDIT_PACK_PRICE, 127 }) 128 } catch (error) { 129 console.error('Purchase error:', error) 130 setIsPurchasing(false) 131 132 // Additional error handling for immediate errors 133 const errorMessage = error instanceof Error ? error.message : 'Purchase failed' 134 if (!errorMessage.includes('User rejected')) { 135 toast.error(t('errors.network.requestFailed'), { 136 description: t('errors.network.requestFailedDesc') 137 }) 138 } 139 } 140 } 141 142 return { 143 // State 144 isConnected, 145 address, 146 isPurchasing, 147 lastPurchase, 148 isLoadingCredits, 149 150 // Refetch function 151 refetchCredits, 152 153 // Data 154 credits: Number(credits || 0), 155 156 // Actions 157 handleBuyCredits, 158 } 159 }