import React, { useState, useMemo, useCallback } from 'react'; import { StyleSheet, Text, TouchableOpacity, type ViewStyle } from 'react-native'; import { useDubsTheme } from '../theme'; import { useDubs } from '../../provider'; import { useGame } from '../../hooks/useGame'; import { useHasClaimed } from '../../hooks/useHasClaimed'; import { ClaimPrizeSheet } from './ClaimPrizeSheet'; import type { ClaimMutationResult } from '../../hooks/useClaim'; export interface ClaimButtonProps { gameId: string; style?: ViewStyle; onSuccess?: (result: ClaimMutationResult) => void; onError?: (error: Error) => void; } /** * Drop-in claim button that handles eligibility checks, prize/refund display, * and the claim sheet lifecycle internally. * * Renders nothing when the user is ineligible (lost, not resolved, not a bettor, etc.). */ export function ClaimButton({ gameId, style, onSuccess, onError }: ClaimButtonProps) { const t = useDubsTheme(); const { wallet } = useDubs(); const game = useGame(gameId); const claimStatus = useHasClaimed(gameId); const [sheetVisible, setSheetVisible] = useState(false); const walletAddress = wallet.publicKey?.toBase58() ?? null; const myBet = useMemo(() => { if (!walletAddress || !game.data?.bettors) return null; return game.data.bettors.find(b => b.wallet === walletAddress) ?? null; }, [walletAddress, game.data?.bettors]); const isResolved = game.data?.isResolved ?? false; const isRefund = isResolved && game.data?.winnerSide === null; const isWinner = isResolved && myBet != null && myBet.team === game.data?.winnerSide; const isEligible = myBet != null && isResolved && (isWinner || isRefund); const prizeAmount = isRefund ? (myBet?.amount ?? 0) : (game.data?.totalPool ?? 0); const handleSuccess = useCallback( (result: ClaimMutationResult) => { claimStatus.refetch(); onSuccess?.(result); }, [claimStatus.refetch, onSuccess], // eslint-disable-line react-hooks/exhaustive-deps ); // Don't render while loading or missing wallet/data if (game.loading || claimStatus.loading || !game.data || !walletAddress) { return null; } const label = isRefund ? 'Refund' : 'Prize'; // Already claimed — always show badge regardless of eligibility computation if (claimStatus.hasClaimed) { return ( {label} Claimed! ); } // Not eligible (lost, not resolved, not a bettor) — render nothing if (!isEligible) { return null; } return ( <> setSheetVisible(true)} > Claim {label} — {prizeAmount} SOL setSheetVisible(false)} gameId={gameId} prizeAmount={prizeAmount} isRefund={isRefund} onSuccess={handleSuccess} onError={onError} /> ); } const styles = StyleSheet.create({ button: { height: 52, borderRadius: 14, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 20, }, buttonText: { color: '#FFFFFF', fontSize: 16, fontWeight: '700', }, badge: { height: 52, borderRadius: 14, borderWidth: 2, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 20, }, badgeText: { fontSize: 16, fontWeight: '700', }, });