import React, { useEffect, useRef } from 'react'; import { View, Text, TouchableOpacity, Animated, StyleSheet, Image, } from 'react-native'; import type { JackpotRound, JackpotLastWinner, JackpotEntry } from '../../types'; import type { ViewStyle } from 'react-native'; export interface JackpotCardProps { round: JackpotRound | null; lastWinner?: JackpotLastWinner | null; entries?: JackpotEntry[]; onPress?: () => void; style?: ViewStyle; } function formatSOL(lamports: string | number): string { const val = typeof lamports === 'string' ? parseInt(lamports, 10) : lamports; if (isNaN(val) || val === 0) return '0'; const sol = val / 1_000_000_000; // No trailing zeros if (sol >= 100) return sol.toFixed(0); if (sol >= 1) return sol.toFixed(2); if (sol >= 0.01) return sol.toFixed(3); return sol.toFixed(4); } function truncateWallet(addr: string): string { if (!addr || addr.length < 8) return addr || ''; return `${addr.slice(0, 4)}...${addr.slice(-4)}`; } export function JackpotCard({ round, lastWinner, entries, onPress, style }: JackpotCardProps) { const shimmerAnim = useRef(new Animated.Value(-1)).current; const pulseAnim = useRef(new Animated.Value(0.4)).current; useEffect(() => { // Shimmer sweep: matches web heroSweep (4s) const shimmer = Animated.loop( Animated.sequence([ Animated.timing(shimmerAnim, { toValue: 1, duration: 4000, useNativeDriver: true }), Animated.delay(500), Animated.timing(shimmerAnim, { toValue: -1, duration: 0, useNativeDriver: true }), ]), ); shimmer.start(); // Status dot pulse const pulse = Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true }), Animated.timing(pulseAnim, { toValue: 0.4, duration: 1000, useNativeDriver: true }), ]), ); pulse.start(); return () => { shimmer.stop(); pulse.stop(); }; }, [shimmerAnim, pulseAnim]); const potSol = round ? formatSOL(round.totalPotLamports) : '0'; const isOpen = round?.status === 'Open'; const entryCount = round?.entryCount ?? 0; const totalWeight = round ? Number(BigInt(round.totalWeight || '0')) : 0; return ( {/* Green accent gradient background */} {/* Shimmer sweep overlay */} {/* Bottom accent bar */} {/* Content */} {/* Status badge */} {isOpen && ( )} {isOpen ? 'Open' : round?.status ?? 'Loading'} {entryCount} player{entryCount !== 1 ? 's' : ''} {/* Hero pot section */} JACKPOT {potSol} SOL 🤑 {/* Info grid: Your Chance / Players / Last Winner */} PLAYERS {entryCount} TOTAL POT {potSol} LAST WIN {lastWinner ? formatSOL(lastWinner.winAmount) : '—'} {/* Player carousel (horizontal scroll of entry chips) */} {entries && entries.length > 0 && ( Players in Round {entries.length} {entries.slice(0, 8).map((entry, i) => { const odds = entry.oddsPercent; return ( {/* Avatar placeholder */} {entry.player.slice(0, 2).toUpperCase()} {truncateWallet(entry.player)} {entry.weightSol.toFixed(2)} SOL {odds}% ); })} {entries.length > 8 && ( +{entries.length - 8} )} )} {/* Place Bet CTA */} Place Bet ); } const styles = StyleSheet.create({ card: { borderRadius: 16, borderWidth: 1, borderColor: 'rgba(34, 197, 94, 0.2)', backgroundColor: '#0c0c14', overflow: 'hidden', position: 'relative', }, gradientBg: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(34, 197, 94, 0.04)', }, shimmer: { position: 'absolute', top: 0, bottom: 0, width: 120, backgroundColor: 'rgba(34, 197, 94, 0.08)', }, accentBar: { position: 'absolute', bottom: 0, left: 0, right: '40%' as any, height: 2, backgroundColor: '#22c55e', }, content: { padding: 16, gap: 12, }, // Status row statusRow: { flexDirection: 'row', alignItems: 'center', gap: 8, }, statusBadge: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 100, }, statusOpen: { backgroundColor: 'rgba(34, 197, 94, 0.15)', }, statusClosed: { backgroundColor: 'rgba(156, 163, 175, 0.15)', }, statusDot: { width: 6, height: 6, borderRadius: 3, backgroundColor: '#22c55e', }, statusText: { fontSize: 12, fontWeight: '700', }, entryCountText: { fontSize: 12, color: '#6b6b6b', fontWeight: '500', }, // Hero pot heroSection: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, potInfo: { flex: 1, }, jackpotLabel: { fontSize: 10, fontWeight: '600', color: '#6b6b6b', letterSpacing: 3, textTransform: 'uppercase', marginBottom: 4, }, potValue: { fontSize: 36, fontWeight: '900', color: '#4ade80', letterSpacing: -1, }, potEmoji: { fontSize: 40, opacity: 0.2, }, // Info grid infoGrid: { flexDirection: 'row', gap: 8, }, infoCard: { flex: 1, borderRadius: 12, borderWidth: 1, borderColor: '#1e1e2a', backgroundColor: '#0c0c14', padding: 12, }, infoLabel: { fontSize: 9, fontWeight: '600', color: '#6b6b6b', letterSpacing: 2, textTransform: 'uppercase', marginBottom: 4, }, infoValue: { fontSize: 18, fontWeight: '700', color: '#FFFFFF', }, // Players section playersSection: { borderRadius: 12, borderWidth: 1, borderColor: '#1e1e2a', backgroundColor: 'rgba(139, 92, 246, 0.04)', padding: 12, overflow: 'hidden', }, playersSectionHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10, }, playersSectionTitle: { fontSize: 13, fontWeight: '600', color: '#a0a0a0', }, activeCountBadge: { backgroundColor: 'rgba(34, 197, 94, 0.15)', paddingHorizontal: 8, paddingVertical: 2, borderRadius: 100, }, activeCountText: { fontSize: 11, fontWeight: '700', color: '#22c55e', }, playersCarousel: { flexDirection: 'row', gap: 10, }, playerCard: { width: 96, borderRadius: 12, borderWidth: 1.5, borderColor: 'rgba(139, 92, 246, 0.4)', backgroundColor: 'rgba(139, 92, 246, 0.08)', padding: 10, alignItems: 'center', gap: 4, }, playerAvatar: { width: 40, height: 40, borderRadius: 20, borderWidth: 1.5, borderColor: '#8b5cf6', backgroundColor: 'rgba(139, 92, 246, 0.2)', alignItems: 'center', justifyContent: 'center', }, playerAvatarText: { fontSize: 14, fontWeight: '700', color: '#a78bfa', }, playerWallet: { fontSize: 10, fontWeight: '500', color: '#a0a0a0', width: '100%', textAlign: 'center', }, playerWager: { fontSize: 12, fontWeight: '600', color: '#a78bfa', }, playerOdds: { fontSize: 10, fontWeight: '700', color: '#22c55e', }, playerCardMore: { width: 96, borderRadius: 12, borderWidth: 1.5, borderColor: '#1e1e2a', backgroundColor: '#14141e', alignItems: 'center', justifyContent: 'center', }, playerMoreText: { fontSize: 16, fontWeight: '700', color: '#6b6b6b', }, // Place bet CTA placeBetButton: { height: 52, borderRadius: 12, alignItems: 'center', justifyContent: 'center', backgroundColor: '#22c55e', shadowColor: '#22c55e', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.25, shadowRadius: 12, elevation: 6, }, placeBetText: { color: '#FFFFFF', fontSize: 16, fontWeight: '700', }, });