/ apps / web / src / hooks / usePurchaseV5.ts
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  }