@bettoredge/calcutta 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/package.json +46 -0
  2. package/src/components/CalcuttaAuction.tsx +453 -0
  3. package/src/components/CalcuttaAuctionItem.tsx +292 -0
  4. package/src/components/CalcuttaBidInput.tsx +214 -0
  5. package/src/components/CalcuttaCard.tsx +131 -0
  6. package/src/components/CalcuttaDetail.tsx +377 -0
  7. package/src/components/CalcuttaEscrow.tsx +464 -0
  8. package/src/components/CalcuttaItemResults.tsx +207 -0
  9. package/src/components/CalcuttaLeaderboard.tsx +179 -0
  10. package/src/components/CalcuttaPayoutPreview.tsx +194 -0
  11. package/src/components/CalcuttaRoundResults.tsx +250 -0
  12. package/src/components/CalcuttaTemplateSelector.tsx +124 -0
  13. package/src/components/sealed/AuctionResultsModal.tsx +165 -0
  14. package/src/components/sealed/EscrowBottomSheet.tsx +185 -0
  15. package/src/components/sealed/SealedBidAuction.tsx +541 -0
  16. package/src/components/sealed/SealedBidHeader.tsx +116 -0
  17. package/src/components/sealed/SealedBidInfoTab.tsx +247 -0
  18. package/src/components/sealed/SealedBidItemCard.tsx +385 -0
  19. package/src/components/sealed/SealedBidItemsTab.tsx +235 -0
  20. package/src/components/sealed/SealedBidMyBidsTab.tsx +512 -0
  21. package/src/components/sealed/SealedBidPlayersTab.tsx +220 -0
  22. package/src/components/sealed/SealedBidStatusBar.tsx +415 -0
  23. package/src/components/sealed/SealedBidTabBar.tsx +172 -0
  24. package/src/helpers/formatting.ts +56 -0
  25. package/src/helpers/lifecycleState.ts +71 -0
  26. package/src/helpers/payout.ts +39 -0
  27. package/src/helpers/validation.ts +64 -0
  28. package/src/hooks/useCalcuttaAuction.ts +164 -0
  29. package/src/hooks/useCalcuttaBid.ts +43 -0
  30. package/src/hooks/useCalcuttaCompetition.ts +63 -0
  31. package/src/hooks/useCalcuttaEscrow.ts +52 -0
  32. package/src/hooks/useCalcuttaItemImages.ts +79 -0
  33. package/src/hooks/useCalcuttaPlayers.ts +46 -0
  34. package/src/hooks/useCalcuttaResults.ts +58 -0
  35. package/src/hooks/useCalcuttaSocket.ts +131 -0
  36. package/src/hooks/useCalcuttaTemplates.ts +36 -0
  37. package/src/index.ts +74 -0
  38. package/src/types.ts +31 -0
@@ -0,0 +1,179 @@
1
+ import React from 'react';
2
+ import { StyleSheet, FlatList, Image } from 'react-native';
3
+ import { View, Text, useTheme } from '@bettoredge/styles';
4
+ import { Ionicons } from '@expo/vector-icons';
5
+ import type { CalcuttaParticipantProps, PublicPlayerProps } from '@bettoredge/types';
6
+ import { formatCurrency, formatPlace } from '../helpers/formatting';
7
+
8
+ export interface CalcuttaLeaderboardProps {
9
+ participants: CalcuttaParticipantProps[];
10
+ players?: Record<string, PublicPlayerProps>;
11
+ player_id?: string;
12
+ market_type: string;
13
+ listHeader?: React.ReactNode;
14
+ }
15
+
16
+ export const CalcuttaLeaderboard: React.FC<CalcuttaLeaderboardProps> = ({
17
+ participants,
18
+ players,
19
+ player_id,
20
+ market_type,
21
+ listHeader,
22
+ }) => {
23
+ const { theme } = useTheme();
24
+
25
+ const sorted = [...participants].sort((a, b) => {
26
+ if (a.place && b.place) return a.place - b.place;
27
+ if (a.place) return -1;
28
+ if (b.place) return 1;
29
+ return b.total_winnings - a.total_winnings;
30
+ });
31
+
32
+ const renderHeader = () => (
33
+ <View variant="transparent">
34
+ <>{listHeader}</>
35
+ <View
36
+ variant="transparent"
37
+ style={[styles.headerRow, { borderColor: theme.colors.border.subtle, backgroundColor: theme.colors.surface.elevated }]}
38
+ >
39
+ <Text variant="caption" color="tertiary" style={styles.placeCol}>#</Text>
40
+ <Text variant="caption" color="tertiary" style={styles.playerCol}>Player</Text>
41
+ <Text variant="caption" color="tertiary" style={styles.numCol}>Items</Text>
42
+ <Text variant="caption" color="tertiary" style={styles.numCol}>Spent</Text>
43
+ <Text variant="caption" color="tertiary" style={styles.numCol}>Won</Text>
44
+ </View>
45
+ </View>
46
+ );
47
+
48
+ const renderItem = ({ item, index }: { item: CalcuttaParticipantProps; index: number }) => {
49
+ const place = item.place ?? index + 1;
50
+ const isPositive = item.total_winnings > item.total_spent;
51
+ const isMe = item.player_id === player_id;
52
+ const player = players?.[item.player_id];
53
+ const username = player?.username || player?.show_name || `Player ${item.player_id.slice(0, 6)}`;
54
+ const profilePic = player?.profile_pic;
55
+
56
+ return (
57
+ <View
58
+ variant="transparent"
59
+ style={[styles.row, { borderColor: theme.colors.border.subtle }, isMe && { backgroundColor: theme.colors.primary.subtle + '40' }]}
60
+ >
61
+ <View variant="transparent" style={styles.placeCol}>
62
+ {place <= 3 ? (
63
+ <Ionicons
64
+ name="trophy"
65
+ size={14}
66
+ color={place === 1 ? '#FFD700' : place === 2 ? '#C0C0C0' : '#CD7F32'}
67
+ />
68
+ ) : (
69
+ <Text variant="caption" color="secondary">{formatPlace(place)}</Text>
70
+ )}
71
+ </View>
72
+ <View variant="transparent" style={styles.playerInfo}>
73
+ {profilePic ? (
74
+ <Image source={{ uri: profilePic }} style={styles.avatar} />
75
+ ) : (
76
+ <View variant="transparent" style={[styles.avatar, { backgroundColor: theme.colors.primary.subtle }]}>
77
+ <Text variant="caption" bold style={{ color: theme.colors.primary.default, fontSize: 10 }}>
78
+ {username.charAt(0).toUpperCase()}
79
+ </Text>
80
+ </View>
81
+ )}
82
+ <View variant="transparent" style={{ flex: 1, marginLeft: 8 }}>
83
+ <View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center' }}>
84
+ <Text variant="body" bold={isMe} numberOfLines={1} style={{ flex: 1 }}>{username}</Text>
85
+ {isMe && (
86
+ <View variant="transparent" style={[styles.youBadge, { backgroundColor: theme.colors.primary.subtle }]}>
87
+ <Text variant="caption" bold style={{ color: theme.colors.primary.default, fontSize: 9 }}>YOU</Text>
88
+ </View>
89
+ )}
90
+ </View>
91
+ </View>
92
+ </View>
93
+ <Text variant="caption" color="secondary" style={styles.numCol}>
94
+ {item.items_owned}
95
+ </Text>
96
+ <Text variant="caption" color="secondary" style={styles.numCol}>
97
+ {formatCurrency(item.total_spent, market_type)}
98
+ </Text>
99
+ <Text
100
+ variant="caption"
101
+ bold={isPositive}
102
+ style={[
103
+ styles.numCol,
104
+ { color: isPositive ? theme.colors.status.success : theme.colors.text.secondary },
105
+ ]}
106
+ >
107
+ {formatCurrency(item.total_winnings, market_type)}
108
+ </Text>
109
+ </View>
110
+ );
111
+ };
112
+
113
+ return (
114
+ <FlatList
115
+ data={sorted}
116
+ keyExtractor={item => item.calcutta_participant_id}
117
+ ListHeaderComponent={renderHeader}
118
+ renderItem={renderItem}
119
+ ListEmptyComponent={
120
+ <Text variant="caption" color="tertiary" style={styles.emptyText}>
121
+ No participants yet
122
+ </Text>
123
+ }
124
+ />
125
+ );
126
+ };
127
+
128
+ const styles = StyleSheet.create({
129
+ headerRow: {
130
+ flexDirection: 'row',
131
+ alignItems: 'center',
132
+ paddingVertical: 8,
133
+ paddingHorizontal: 12,
134
+ borderBottomWidth: 1,
135
+ },
136
+ row: {
137
+ flexDirection: 'row',
138
+ alignItems: 'center',
139
+ paddingVertical: 10,
140
+ paddingHorizontal: 12,
141
+ borderBottomWidth: 1,
142
+ },
143
+ placeCol: {
144
+ width: 32,
145
+ alignItems: 'center',
146
+ },
147
+ playerInfo: {
148
+ flex: 1,
149
+ flexDirection: 'row',
150
+ alignItems: 'center',
151
+ marginLeft: 4,
152
+ },
153
+ playerCol: {
154
+ flex: 1,
155
+ marginLeft: 4,
156
+ },
157
+ avatar: {
158
+ width: 28,
159
+ height: 28,
160
+ borderRadius: 14,
161
+ alignItems: 'center',
162
+ justifyContent: 'center',
163
+ overflow: 'hidden',
164
+ },
165
+ youBadge: {
166
+ paddingHorizontal: 5,
167
+ paddingVertical: 1,
168
+ borderRadius: 4,
169
+ marginLeft: 4,
170
+ },
171
+ numCol: {
172
+ width: 60,
173
+ textAlign: 'right',
174
+ },
175
+ emptyText: {
176
+ textAlign: 'center',
177
+ padding: 20,
178
+ },
179
+ });
@@ -0,0 +1,194 @@
1
+ import React from 'react';
2
+ import { StyleSheet, FlatList } from 'react-native';
3
+ import { View, Text, useTheme } from '@bettoredge/styles';
4
+ import { Ionicons } from '@expo/vector-icons';
5
+ import type { CalcuttaPayoutRuleProps } from '@bettoredge/types';
6
+ import { formatCurrency } from '../helpers/formatting';
7
+
8
+ export interface CalcuttaPayoutPreviewProps {
9
+ payout_rules: CalcuttaPayoutRuleProps[];
10
+ total_pot: number;
11
+ market_type: string;
12
+ unclaimed_pot?: number;
13
+ min_spend_pct?: number;
14
+ }
15
+
16
+ export const CalcuttaPayoutPreview: React.FC<CalcuttaPayoutPreviewProps> = ({
17
+ payout_rules,
18
+ total_pot,
19
+ market_type,
20
+ unclaimed_pot,
21
+ min_spend_pct,
22
+ }) => {
23
+ const { theme } = useTheme();
24
+
25
+ const roundRules = payout_rules
26
+ .filter(r => r.payout_type === 'round')
27
+ .sort((a, b) => (a.round_number ?? 0) - (b.round_number ?? 0));
28
+
29
+ const placementRules = payout_rules
30
+ .filter(r => r.payout_type === 'placement')
31
+ .sort((a, b) => (a.placement ?? 0) - (b.placement ?? 0));
32
+
33
+ const totalPct = payout_rules.reduce((sum, r) => sum + r.payout_pct, 0);
34
+
35
+ const renderRule = (rule: CalcuttaPayoutRuleProps) => {
36
+ const amount = (rule.payout_pct / 100) * total_pot;
37
+ return (
38
+ <View
39
+ key={rule.calcutta_payout_rule_id}
40
+ variant="transparent"
41
+ style={[styles.ruleRow, { borderColor: theme.colors.border.subtle }]}
42
+ >
43
+ <View variant="transparent" style={styles.ruleInfo}>
44
+ <Text variant="body" numberOfLines={1}>
45
+ {rule.description ?? `${rule.payout_type === 'round' ? `Round ${rule.round_number}` : `${rule.placement}${getOrdinal(rule.placement ?? 0)} Place`}`}
46
+ </Text>
47
+ <Text variant="caption" color="tertiary">
48
+ {rule.payout_type === 'round' ? 'Round Payout' : 'Placement Payout'}
49
+ </Text>
50
+ </View>
51
+ <View variant="transparent" style={styles.ruleAmounts}>
52
+ <Text variant="body" bold>
53
+ {formatCurrency(amount, market_type)}
54
+ </Text>
55
+ <Text variant="caption" color="tertiary">
56
+ {rule.payout_pct.toFixed(1)}%
57
+ </Text>
58
+ </View>
59
+ </View>
60
+ );
61
+ };
62
+
63
+ return (
64
+ <View variant="transparent" style={styles.container}>
65
+ {/* Total pot summary */}
66
+ <View
67
+ variant="transparent"
68
+ style={[styles.potSummary, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}
69
+ >
70
+ <Ionicons name="cash-outline" size={20} color={theme.colors.primary.default} />
71
+ <View variant="transparent" style={styles.potInfo}>
72
+ <Text variant="caption" color="tertiary">Total Pot</Text>
73
+ <Text variant="body" bold>
74
+ {formatCurrency(total_pot, market_type)}
75
+ </Text>
76
+ </View>
77
+ <View variant="transparent" style={styles.potInfo}>
78
+ <Text variant="caption" color="tertiary">Allocated</Text>
79
+ <Text variant="body" bold>
80
+ {totalPct.toFixed(1)}%
81
+ </Text>
82
+ </View>
83
+ </View>
84
+
85
+ {/* Min spend rule note */}
86
+ {min_spend_pct != null && Number(min_spend_pct) > 0 && (
87
+ <View
88
+ variant="transparent"
89
+ style={[styles.rolloverBanner, { backgroundColor: theme.colors.primary.subtle, borderColor: theme.colors.border.subtle }]}
90
+ >
91
+ <Ionicons name="alert-circle-outline" size={16} color={theme.colors.primary.default} />
92
+ <View variant="transparent" style={{ marginLeft: 8, flex: 1 }}>
93
+ <Text variant="caption" style={{ color: theme.colors.text.secondary }}>
94
+ Min spend rule active: {Number(min_spend_pct)}%. If you deposit $300 but only bid $200, the ${((Number(min_spend_pct) / 100) * 300 - 200).toFixed(0)} shortfall is added to the pot.
95
+ </Text>
96
+ </View>
97
+ </View>
98
+ )}
99
+
100
+ {/* Unclaimed rollover notice */}
101
+ {(unclaimed_pot ?? 0) > 0 && (
102
+ <View
103
+ variant="transparent"
104
+ style={[styles.rolloverBanner, { backgroundColor: theme.colors.primary.subtle, borderColor: theme.colors.border.subtle }]}
105
+ >
106
+ <Ionicons name="swap-horizontal-outline" size={16} color={theme.colors.primary.default} />
107
+ <View variant="transparent" style={{ marginLeft: 8, flex: 1 }}>
108
+ <Text variant="caption" bold style={{ color: theme.colors.primary.default }}>
109
+ {formatCurrency(unclaimed_pot ?? 0, market_type)} rolling forward
110
+ </Text>
111
+ <Text variant="caption" style={{ color: theme.colors.text.secondary, marginTop: 2 }}>
112
+ From unbid teams that advanced. This amount is added to the next round's pool. After the final round, any remaining amount is split among all players based on how much they spent.
113
+ </Text>
114
+ </View>
115
+ </View>
116
+ )}
117
+
118
+ {/* Round-based payouts */}
119
+ {roundRules.length > 0 && (
120
+ <>
121
+ <View variant="transparent" style={styles.sectionHeader}>
122
+ <Text variant="caption" bold color="secondary">Round Payouts</Text>
123
+ </View>
124
+ {roundRules.map(renderRule)}
125
+ </>
126
+ )}
127
+
128
+ {/* Placement-based payouts */}
129
+ {placementRules.length > 0 && (
130
+ <>
131
+ <View variant="transparent" style={styles.sectionHeader}>
132
+ <Text variant="caption" bold color="secondary">Placement Payouts</Text>
133
+ </View>
134
+ {placementRules.map(renderRule)}
135
+ </>
136
+ )}
137
+
138
+ {payout_rules.length === 0 && (
139
+ <Text variant="caption" color="tertiary" style={styles.emptyText}>
140
+ No payout rules defined
141
+ </Text>
142
+ )}
143
+ </View>
144
+ );
145
+ };
146
+
147
+ const getOrdinal = (n: number): string => {
148
+ const suffixes = ['th', 'st', 'nd', 'rd'];
149
+ const v = n % 100;
150
+ return (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]);
151
+ };
152
+
153
+ const styles = StyleSheet.create({
154
+ container: {
155
+ flex: 1,
156
+ },
157
+ potSummary: {
158
+ flexDirection: 'row',
159
+ alignItems: 'center',
160
+ padding: 14,
161
+ borderBottomWidth: 1,
162
+ },
163
+ potInfo: {
164
+ marginLeft: 16,
165
+ },
166
+ sectionHeader: {
167
+ paddingHorizontal: 12,
168
+ paddingVertical: 8,
169
+ },
170
+ ruleRow: {
171
+ flexDirection: 'row',
172
+ alignItems: 'center',
173
+ paddingVertical: 10,
174
+ paddingHorizontal: 12,
175
+ borderBottomWidth: 1,
176
+ },
177
+ ruleInfo: {
178
+ flex: 1,
179
+ },
180
+ ruleAmounts: {
181
+ alignItems: 'flex-end',
182
+ marginLeft: 10,
183
+ },
184
+ emptyText: {
185
+ textAlign: 'center',
186
+ padding: 20,
187
+ },
188
+ rolloverBanner: {
189
+ flexDirection: 'row',
190
+ alignItems: 'flex-start',
191
+ padding: 12,
192
+ borderBottomWidth: 1,
193
+ },
194
+ });
@@ -0,0 +1,250 @@
1
+ import React, { useState } from 'react';
2
+ import { StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
3
+ import { View, Text, useTheme } from '@bettoredge/styles';
4
+ import { Ionicons } from '@expo/vector-icons';
5
+ import type {
6
+ CalcuttaRoundProps,
7
+ CalcuttaAuctionItemProps,
8
+ CalcuttaItemResultProps,
9
+ CalcuttaPayoutRuleProps,
10
+ } from '@bettoredge/types';
11
+ import { useCalcuttaResults, RoundResultSummary } from '../hooks/useCalcuttaResults';
12
+ import { formatCurrency, getStatusLabel } from '../helpers/formatting';
13
+
14
+ export interface CalcuttaRoundResultsProps {
15
+ rounds: CalcuttaRoundProps[];
16
+ items: CalcuttaAuctionItemProps[];
17
+ item_results: CalcuttaItemResultProps[];
18
+ payout_rules: CalcuttaPayoutRuleProps[];
19
+ market_type: string;
20
+ total_pot?: number;
21
+ unclaimed_pot?: number;
22
+ }
23
+
24
+ export const CalcuttaRoundResults: React.FC<CalcuttaRoundResultsProps> = ({
25
+ rounds,
26
+ items,
27
+ item_results,
28
+ payout_rules,
29
+ market_type,
30
+ total_pot,
31
+ unclaimed_pot,
32
+ }) => {
33
+ const { theme } = useTheme();
34
+ const { roundSummaries } = useCalcuttaResults(rounds, items, item_results, payout_rules);
35
+ const [expandedRound, setExpandedRound] = useState<string | null>(null);
36
+
37
+ if (rounds.length === 0) {
38
+ return (
39
+ <View variant="transparent" style={styles.emptyContainer}>
40
+ <Text variant="caption" color="tertiary">No rounds yet</Text>
41
+ </View>
42
+ );
43
+ }
44
+
45
+ const toggleRound = (roundId: string) => {
46
+ setExpandedRound(prev => prev === roundId ? null : roundId);
47
+ };
48
+
49
+ const renderRound = (summary: RoundResultSummary) => {
50
+ const isExpanded = expandedRound === summary.round.calcutta_round_id;
51
+ const statusColor = summary.round.status === 'closed'
52
+ ? theme.colors.status.success
53
+ : summary.round.status === 'inprogress'
54
+ ? theme.colors.primary.default
55
+ : theme.colors.text.tertiary;
56
+
57
+ return (
58
+ <View
59
+ key={summary.round.calcutta_round_id}
60
+ variant="transparent"
61
+ style={[styles.roundCard, { borderColor: theme.colors.border.subtle }]}
62
+ >
63
+ <TouchableOpacity
64
+ style={styles.roundHeader}
65
+ onPress={() => toggleRound(summary.round.calcutta_round_id)}
66
+ activeOpacity={0.7}
67
+ >
68
+ <View variant="transparent" style={styles.roundInfo}>
69
+ <Text variant="body" bold>{summary.round.round_name}</Text>
70
+ <View variant="transparent" style={styles.roundMeta}>
71
+ <View variant="transparent" style={[styles.statusDot, { backgroundColor: statusColor }]} />
72
+ <Text variant="caption" color="tertiary">
73
+ {getStatusLabel(summary.round.status)}
74
+ </Text>
75
+ {summary.total_payout > 0 && (
76
+ <Text variant="caption" style={{ color: theme.colors.status.success, marginLeft: 8 }}>
77
+ {formatCurrency(summary.total_payout, market_type)} paid
78
+ </Text>
79
+ )}
80
+ </View>
81
+ </View>
82
+ <View variant="transparent" style={styles.roundCounts}>
83
+ <Text variant="caption" bold style={{ color: theme.colors.status.success }}>
84
+ {summary.advanced_items.length}/{summary.advanced_items.length + summary.eliminated_items.length}
85
+ </Text>
86
+ <Text variant="caption" color="tertiary" style={{ marginLeft: 4 }}>advanced</Text>
87
+ </View>
88
+ <Ionicons
89
+ name={isExpanded ? 'chevron-up' : 'chevron-down'}
90
+ size={16}
91
+ color={theme.colors.text.tertiary}
92
+ style={styles.chevron}
93
+ />
94
+ </TouchableOpacity>
95
+
96
+ {isExpanded && (
97
+ <View variant="transparent" style={[styles.roundBody, { borderColor: theme.colors.border.subtle }]}>
98
+ {/* Payout rule */}
99
+ {summary.payout_rule && (
100
+ <View variant="transparent" style={styles.payoutRow}>
101
+ <Ionicons name="cash-outline" size={14} color={theme.colors.text.tertiary} />
102
+ <Text variant="caption" color="tertiary" style={{ marginLeft: 6 }}>
103
+ {summary.payout_rule.payout_pct}% of pot split among advancing teams
104
+ </Text>
105
+ </View>
106
+ )}
107
+
108
+ {/* Rollover notice for unsold advancing items */}
109
+ {summary.round.status === 'closed' && summary.advanced_items.some(i => !i.winning_player_id) && (
110
+ <View variant="transparent" style={[styles.payoutRow, { marginTop: 2 }]}>
111
+ <Ionicons name="arrow-forward-circle-outline" size={14} color={theme.colors.primary.default} />
112
+ <Text variant="caption" style={{ marginLeft: 6, color: theme.colors.primary.default, flex: 1 }}>
113
+ Unbid teams' share rolls into the next round. Any amount remaining after the final round is redistributed to all players proportionally.
114
+ </Text>
115
+ </View>
116
+ )}
117
+
118
+ {/* Advanced items */}
119
+ {summary.advanced_items.length > 0 && (
120
+ <>
121
+ <Text variant="caption" bold color="secondary" style={styles.sectionLabel}>
122
+ Advanced ({summary.advanced_items.length})
123
+ </Text>
124
+ {summary.advanced_items.map(item => {
125
+ const isUnsold = !item.winning_player_id;
126
+ return (
127
+ <View key={item.calcutta_auction_item_id} variant="transparent" style={styles.itemRow}>
128
+ <Ionicons
129
+ name={isUnsold ? 'help-circle' : 'checkmark-circle'}
130
+ size={14}
131
+ color={isUnsold ? theme.colors.text.tertiary : theme.colors.status.success}
132
+ />
133
+ <Text variant="caption" style={[styles.itemName, isUnsold && { color: theme.colors.text.tertiary }]} numberOfLines={1}>
134
+ {item.item_name}{isUnsold ? ' (unbid)' : ''}
135
+ </Text>
136
+ {item.seed != null && (
137
+ <Text variant="caption" color="tertiary">#{item.seed}</Text>
138
+ )}
139
+ </View>
140
+ );
141
+ })}
142
+ </>
143
+ )}
144
+
145
+ {/* Eliminated items */}
146
+ {summary.eliminated_items.length > 0 && (
147
+ <>
148
+ <Text variant="caption" bold color="secondary" style={styles.sectionLabel}>
149
+ Eliminated ({summary.eliminated_items.length})
150
+ </Text>
151
+ {summary.eliminated_items.map(item => {
152
+ const isUnsold = !item.winning_player_id;
153
+ return (
154
+ <View key={item.calcutta_auction_item_id} variant="transparent" style={styles.itemRow}>
155
+ <Ionicons name="close-circle" size={14} color={theme.colors.status.error} />
156
+ <Text variant="caption" style={[styles.itemName, isUnsold && { color: theme.colors.text.tertiary }]} numberOfLines={1}>
157
+ {item.item_name}{isUnsold ? ' (unbid)' : ''}
158
+ </Text>
159
+ {item.seed != null && (
160
+ <Text variant="caption" color="tertiary">#{item.seed}</Text>
161
+ )}
162
+ </View>
163
+ );
164
+ })}
165
+ </>
166
+ )}
167
+
168
+ {summary.advanced_items.length === 0 && summary.eliminated_items.length === 0 && (
169
+ <Text variant="caption" color="tertiary" style={styles.noResults}>
170
+ No results yet for this round
171
+ </Text>
172
+ )}
173
+ </View>
174
+ )}
175
+ </View>
176
+ );
177
+ };
178
+
179
+ return (
180
+ <ScrollView style={styles.container}>
181
+ {roundSummaries.map(renderRound)}
182
+ </ScrollView>
183
+ );
184
+ };
185
+
186
+ const styles = StyleSheet.create({
187
+ container: {
188
+ flex: 1,
189
+ },
190
+ emptyContainer: {
191
+ alignItems: 'center',
192
+ padding: 20,
193
+ },
194
+ roundCard: {
195
+ borderBottomWidth: 1,
196
+ },
197
+ roundHeader: {
198
+ flexDirection: 'row',
199
+ alignItems: 'center',
200
+ padding: 12,
201
+ },
202
+ roundInfo: {
203
+ flex: 1,
204
+ },
205
+ roundMeta: {
206
+ flexDirection: 'row',
207
+ alignItems: 'center',
208
+ marginTop: 4,
209
+ },
210
+ statusDot: {
211
+ width: 6,
212
+ height: 6,
213
+ borderRadius: 3,
214
+ marginRight: 6,
215
+ },
216
+ roundCounts: {
217
+ marginHorizontal: 10,
218
+ },
219
+ chevron: {
220
+ marginLeft: 4,
221
+ },
222
+ roundBody: {
223
+ paddingHorizontal: 12,
224
+ paddingBottom: 12,
225
+ borderTopWidth: 1,
226
+ },
227
+ payoutRow: {
228
+ flexDirection: 'row',
229
+ alignItems: 'center',
230
+ paddingVertical: 8,
231
+ },
232
+ sectionLabel: {
233
+ marginTop: 8,
234
+ marginBottom: 4,
235
+ },
236
+ itemRow: {
237
+ flexDirection: 'row',
238
+ alignItems: 'center',
239
+ paddingVertical: 4,
240
+ paddingLeft: 4,
241
+ },
242
+ itemName: {
243
+ flex: 1,
244
+ marginLeft: 6,
245
+ },
246
+ noResults: {
247
+ paddingVertical: 8,
248
+ textAlign: 'center',
249
+ },
250
+ });