@bettoredge/calcutta 0.4.1 โ 0.4.2
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.
- package/package.json +1 -1
- package/src/components/CalcuttaActionCard.tsx +31 -2
- package/src/components/CalcuttaAuction.tsx +0 -7
- package/src/components/CalcuttaDetail.tsx +247 -168
- package/src/components/CalcuttaResults.tsx +461 -0
- package/src/components/CalcuttaWalkthrough.tsx +371 -0
- package/src/components/sealed/SealedBidAuction.tsx +140 -158
- package/src/index.ts +4 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import React, { useState, useMemo, useEffect } from 'react';
|
|
2
|
+
import { StyleSheet, ScrollView, TouchableOpacity, Image, FlatList, Platform, useWindowDimensions } from 'react-native';
|
|
3
|
+
import { View, Text, useTheme } from '@bettoredge/styles';
|
|
4
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
5
|
+
import type { CalcuttaParticipantProps } from '@bettoredge/types';
|
|
6
|
+
import { useCalcuttaCompetition } from '../hooks/useCalcuttaCompetition';
|
|
7
|
+
import { useCalcuttaPlayers } from '../hooks/useCalcuttaPlayers';
|
|
8
|
+
import { useCalcuttaItemImages } from '../hooks/useCalcuttaItemImages';
|
|
9
|
+
import { useCalcuttaResults } from '../hooks/useCalcuttaResults';
|
|
10
|
+
import { useCalcuttaSocket } from '../hooks/useCalcuttaSocket';
|
|
11
|
+
import { formatCurrency, getStatusLabel, resolveItemImageUrl } from '../helpers/formatting';
|
|
12
|
+
|
|
13
|
+
export interface CalcuttaResultsProps {
|
|
14
|
+
calcutta_competition_id: string;
|
|
15
|
+
player_id?: string;
|
|
16
|
+
access_token?: string;
|
|
17
|
+
device_id?: string;
|
|
18
|
+
player_username?: string;
|
|
19
|
+
player_profile_pic?: string;
|
|
20
|
+
onManage?: () => void;
|
|
21
|
+
onShare?: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const CalcuttaResults: React.FC<CalcuttaResultsProps> = ({
|
|
25
|
+
calcutta_competition_id,
|
|
26
|
+
player_id,
|
|
27
|
+
access_token,
|
|
28
|
+
device_id,
|
|
29
|
+
player_username,
|
|
30
|
+
player_profile_pic,
|
|
31
|
+
onManage,
|
|
32
|
+
onShare,
|
|
33
|
+
}) => {
|
|
34
|
+
const { theme } = useTheme();
|
|
35
|
+
const { width: windowWidth } = useWindowDimensions();
|
|
36
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
37
|
+
const measuredWidth = containerWidth || windowWidth;
|
|
38
|
+
const isDesktop = Platform.OS === 'web' && measuredWidth >= 700;
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
loading,
|
|
42
|
+
competition,
|
|
43
|
+
rounds,
|
|
44
|
+
items,
|
|
45
|
+
participants,
|
|
46
|
+
payout_rules,
|
|
47
|
+
item_results,
|
|
48
|
+
refresh,
|
|
49
|
+
} = useCalcuttaCompetition(calcutta_competition_id);
|
|
50
|
+
|
|
51
|
+
const participantIds = useMemo(() => participants.map(p => p.player_id), [participants]);
|
|
52
|
+
const { players: enrichedPlayers } = useCalcuttaPlayers(participantIds);
|
|
53
|
+
const { images: itemImages } = useCalcuttaItemImages(items);
|
|
54
|
+
const { roundSummaries } = useCalcuttaResults(rounds, items, item_results, payout_rules);
|
|
55
|
+
|
|
56
|
+
const { presence, socketState } = useCalcuttaSocket(
|
|
57
|
+
calcutta_competition_id,
|
|
58
|
+
access_token,
|
|
59
|
+
device_id,
|
|
60
|
+
{ username: player_username, profile_pic: player_profile_pic },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Derived data
|
|
64
|
+
const isAdmin = player_id != null && competition?.admin_id == player_id;
|
|
65
|
+
const myItems = useMemo(() =>
|
|
66
|
+
items.filter(i => i.winning_player_id == player_id).sort((a, b) => Number(b.winning_bid || 0) - Number(a.winning_bid || 0)),
|
|
67
|
+
[items, player_id]
|
|
68
|
+
);
|
|
69
|
+
const totalPot = useMemo(() =>
|
|
70
|
+
items.filter(i => i.status === 'sold').reduce((sum, i) => sum + Number(i.winning_bid || 0), 0) || Number(competition?.total_pot) || 0,
|
|
71
|
+
[items, competition?.total_pot]
|
|
72
|
+
);
|
|
73
|
+
const myTotalSpent = useMemo(() => myItems.reduce((sum, i) => sum + Number(i.winning_bid || 0), 0), [myItems]);
|
|
74
|
+
const myTotalEarned = useMemo(() => {
|
|
75
|
+
if (!player_id) return 0;
|
|
76
|
+
return item_results
|
|
77
|
+
.filter(r => {
|
|
78
|
+
const item = items.find(i => i.calcutta_auction_item_id === r.calcutta_auction_item_id);
|
|
79
|
+
return item?.winning_player_id == player_id;
|
|
80
|
+
})
|
|
81
|
+
.reduce((sum, r) => sum + Number(r.payout_earned || 0), 0);
|
|
82
|
+
}, [item_results, items, player_id]);
|
|
83
|
+
|
|
84
|
+
const statusLabel = competition?.status === 'closed' ? 'Completed'
|
|
85
|
+
: competition?.status === 'inprogress' ? 'In Progress'
|
|
86
|
+
: 'Auction Closed';
|
|
87
|
+
const statusColor = competition?.status === 'closed' ? '#6B7280'
|
|
88
|
+
: competition?.status === 'inprogress' ? '#8B5CF6'
|
|
89
|
+
: '#F59E0B';
|
|
90
|
+
|
|
91
|
+
// Sorted leaderboard
|
|
92
|
+
const leaderboard = useMemo(() =>
|
|
93
|
+
[...participants].sort((a, b) => {
|
|
94
|
+
if (a.place && b.place) return a.place - b.place;
|
|
95
|
+
if (a.place) return -1;
|
|
96
|
+
if (b.place) return 1;
|
|
97
|
+
return Number(b.total_winnings) - Number(a.total_winnings);
|
|
98
|
+
}),
|
|
99
|
+
[participants]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Expandable rounds
|
|
103
|
+
const [expandedRound, setExpandedRound] = useState<number | null>(null);
|
|
104
|
+
|
|
105
|
+
// Item search
|
|
106
|
+
const [itemSearch, setItemSearch] = useState('');
|
|
107
|
+
const filteredItems = useMemo(() => {
|
|
108
|
+
if (!itemSearch.trim()) return items;
|
|
109
|
+
const q = itemSearch.toLowerCase();
|
|
110
|
+
return items.filter(i => {
|
|
111
|
+
if (i.item_name.toLowerCase().includes(q)) return true;
|
|
112
|
+
if (i.winning_player_id) {
|
|
113
|
+
const owner = enrichedPlayers[i.winning_player_id];
|
|
114
|
+
if ((owner?.username || '').toLowerCase().includes(q)) return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
118
|
+
}, [items, itemSearch, enrichedPlayers]);
|
|
119
|
+
|
|
120
|
+
if (loading && !competition) {
|
|
121
|
+
return (
|
|
122
|
+
<View variant="transparent" style={[styles.container, { backgroundColor: theme.colors.surface.base, alignItems: 'center', justifyContent: 'center' }]}>
|
|
123
|
+
<Text variant="body" color="secondary">Loading...</Text>
|
|
124
|
+
</View>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!competition) {
|
|
129
|
+
return (
|
|
130
|
+
<View variant="transparent" style={[styles.container, { backgroundColor: theme.colors.surface.base, alignItems: 'center', justifyContent: 'center' }]}>
|
|
131
|
+
<Text variant="body" color="secondary">Competition not found</Text>
|
|
132
|
+
</View>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const marketType = competition.market_type;
|
|
137
|
+
const isSweepstakes = competition.auction_type === 'sweepstakes';
|
|
138
|
+
|
|
139
|
+
// โโโ RENDER HELPERS โโโ
|
|
140
|
+
|
|
141
|
+
const renderHeader = () => (
|
|
142
|
+
<View variant="transparent" style={{ paddingHorizontal: 16, paddingVertical: 12 }}>
|
|
143
|
+
<View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
144
|
+
<View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', backgroundColor: statusColor + '20', paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12 }}>
|
|
145
|
+
<View variant="transparent" style={{ width: 6, height: 6, borderRadius: 3, backgroundColor: statusColor, marginRight: 6 }} />
|
|
146
|
+
<Text variant="caption" bold style={{ color: statusColor, fontSize: 11, lineHeight: 15 }}>{statusLabel}</Text>
|
|
147
|
+
</View>
|
|
148
|
+
<Text variant="caption" color="tertiary" style={{ marginLeft: 8 }}>
|
|
149
|
+
{isSweepstakes ? 'Sweepstakes' : competition.auction_type === 'live' ? 'Live Auction' : 'Sealed Bid'}
|
|
150
|
+
</Text>
|
|
151
|
+
<View variant="transparent" style={{ flex: 1 }} />
|
|
152
|
+
{isAdmin && onManage && (
|
|
153
|
+
<TouchableOpacity
|
|
154
|
+
onPress={onManage}
|
|
155
|
+
style={{ flexDirection: 'row', alignItems: 'center', backgroundColor: theme.colors.surface.elevated, paddingHorizontal: 10, paddingVertical: 5, borderRadius: 8, borderWidth: 1, borderColor: theme.colors.border.subtle }}
|
|
156
|
+
activeOpacity={0.7}
|
|
157
|
+
>
|
|
158
|
+
<Ionicons name="settings-outline" size={14} color={theme.colors.text.secondary} />
|
|
159
|
+
<Text variant="caption" color="secondary" style={{ marginLeft: 4 }}>Manage</Text>
|
|
160
|
+
</TouchableOpacity>
|
|
161
|
+
)}
|
|
162
|
+
</View>
|
|
163
|
+
<Text variant="h3" bold style={{ marginTop: 8 }}>{competition.competition_name}</Text>
|
|
164
|
+
{competition.competition_description ? (
|
|
165
|
+
<Text variant="caption" color="secondary" style={{ marginTop: 4 }}>{competition.competition_description}</Text>
|
|
166
|
+
) : null}
|
|
167
|
+
</View>
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const renderMyItems = () => {
|
|
171
|
+
if (myItems.length === 0) return null;
|
|
172
|
+
return (
|
|
173
|
+
<View variant="transparent" style={{ paddingHorizontal: 16 }}>
|
|
174
|
+
<Text variant="caption" bold color="tertiary" style={styles.sectionLabel}>
|
|
175
|
+
Your Items ({myItems.length})
|
|
176
|
+
</Text>
|
|
177
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}>
|
|
178
|
+
{myItems.map(mi => {
|
|
179
|
+
const imgUrl = resolveItemImageUrl(mi.item_image) || itemImages[mi.item_id]?.url;
|
|
180
|
+
const isEliminated = mi.status === 'eliminated';
|
|
181
|
+
return (
|
|
182
|
+
<View key={mi.calcutta_auction_item_id} variant="transparent" style={{
|
|
183
|
+
backgroundColor: theme.colors.surface.elevated, borderColor: isEliminated ? theme.colors.status.error + '40' : theme.colors.border.subtle,
|
|
184
|
+
borderWidth: 1, borderRadius: 10, padding: 10, alignItems: 'center', width: 110, opacity: isEliminated ? 0.6 : 1,
|
|
185
|
+
}}>
|
|
186
|
+
{imgUrl ? (
|
|
187
|
+
<Image source={{ uri: imgUrl }} style={{ width: 40, height: 40, borderRadius: 8, marginBottom: 6 }} resizeMode="cover" />
|
|
188
|
+
) : (
|
|
189
|
+
<Ionicons name="trophy" size={24} color={isEliminated ? theme.colors.text.tertiary : theme.colors.primary.default} style={{ marginBottom: 6 }} />
|
|
190
|
+
)}
|
|
191
|
+
<Text variant="caption" bold numberOfLines={2} style={{ textAlign: 'center', fontSize: 12, lineHeight: 16 }}>{mi.item_name}</Text>
|
|
192
|
+
{mi.seed != null && <Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>#{mi.seed}</Text>}
|
|
193
|
+
{Number(mi.winning_bid) > 0 && !isSweepstakes && (
|
|
194
|
+
<Text variant="caption" bold style={{ color: '#10B981', marginTop: 2, fontSize: 11, lineHeight: 15 }}>{formatCurrency(mi.winning_bid, marketType)}</Text>
|
|
195
|
+
)}
|
|
196
|
+
{isEliminated && (
|
|
197
|
+
<Text variant="caption" style={{ color: theme.colors.status.error, fontSize: 9, lineHeight: 12, marginTop: 2 }}>Eliminated</Text>
|
|
198
|
+
)}
|
|
199
|
+
</View>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
</ScrollView>
|
|
203
|
+
</View>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const renderPotKPIs = () => (
|
|
208
|
+
<View variant="transparent" style={{ flexDirection: 'row', paddingHorizontal: 16, paddingVertical: 8, gap: 10 }}>
|
|
209
|
+
{!isSweepstakes && (
|
|
210
|
+
<View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
211
|
+
<Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>Pot</Text>
|
|
212
|
+
<Text variant="h3" bold style={{ color: '#F59E0B' }}>{formatCurrency(totalPot, marketType)}</Text>
|
|
213
|
+
</View>
|
|
214
|
+
)}
|
|
215
|
+
{!isSweepstakes && myTotalSpent > 0 && (
|
|
216
|
+
<View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
217
|
+
<Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>You Spent</Text>
|
|
218
|
+
<Text variant="h3" bold>{formatCurrency(myTotalSpent, marketType)}</Text>
|
|
219
|
+
</View>
|
|
220
|
+
)}
|
|
221
|
+
{myTotalEarned > 0 && (
|
|
222
|
+
<View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
223
|
+
<Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>You Earned</Text>
|
|
224
|
+
<Text variant="h3" bold style={{ color: '#10B981' }}>{formatCurrency(myTotalEarned, marketType)}</Text>
|
|
225
|
+
</View>
|
|
226
|
+
)}
|
|
227
|
+
<View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
228
|
+
<Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>Players</Text>
|
|
229
|
+
<Text variant="h3" bold>{participants.length}</Text>
|
|
230
|
+
</View>
|
|
231
|
+
</View>
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const renderRounds = () => {
|
|
235
|
+
if (roundSummaries.length === 0) return null;
|
|
236
|
+
const sortedRounds = [...roundSummaries].sort((a, b) => a.round.round_number - b.round.round_number);
|
|
237
|
+
return (
|
|
238
|
+
<View variant="transparent" style={{ paddingHorizontal: 16 }}>
|
|
239
|
+
<Text variant="caption" bold color="tertiary" style={styles.sectionLabel}>Rounds</Text>
|
|
240
|
+
{sortedRounds.map(rs => {
|
|
241
|
+
const isExpanded = expandedRound === rs.round.round_number;
|
|
242
|
+
const isClosed = rs.round.status === 'closed';
|
|
243
|
+
return (
|
|
244
|
+
<TouchableOpacity
|
|
245
|
+
key={rs.round.calcutta_round_id}
|
|
246
|
+
style={[styles.roundCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}
|
|
247
|
+
onPress={() => setExpandedRound(isExpanded ? null : rs.round.round_number)}
|
|
248
|
+
activeOpacity={0.7}
|
|
249
|
+
>
|
|
250
|
+
<View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
251
|
+
<Ionicons name={isClosed ? 'checkmark-circle' : 'ellipse-outline'} size={16} color={isClosed ? '#10B981' : theme.colors.text.tertiary} />
|
|
252
|
+
<Text variant="body" bold style={{ flex: 1, marginLeft: 8 }}>{rs.round.round_name}</Text>
|
|
253
|
+
{rs.payout_rule && (
|
|
254
|
+
<Text variant="caption" bold style={{ color: '#F59E0B' }}>{rs.payout_rule.payout_pct}%</Text>
|
|
255
|
+
)}
|
|
256
|
+
{rs.total_payout > 0 && (
|
|
257
|
+
<Text variant="caption" bold style={{ color: '#10B981', marginLeft: 8 }}>{formatCurrency(rs.total_payout, marketType)}</Text>
|
|
258
|
+
)}
|
|
259
|
+
<Ionicons name={isExpanded ? 'chevron-up' : 'chevron-down'} size={14} color={theme.colors.text.tertiary} style={{ marginLeft: 8 }} />
|
|
260
|
+
</View>
|
|
261
|
+
{!isExpanded && isClosed && (
|
|
262
|
+
<Text variant="caption" color="tertiary" style={{ marginTop: 4, fontSize: 11, lineHeight: 15 }}>
|
|
263
|
+
{rs.advanced_items.length} advanced ยท {rs.eliminated_items.length} eliminated
|
|
264
|
+
</Text>
|
|
265
|
+
)}
|
|
266
|
+
{isExpanded && (
|
|
267
|
+
<View variant="transparent" style={{ marginTop: 10 }}>
|
|
268
|
+
{rs.advanced_items.length > 0 && (
|
|
269
|
+
<>
|
|
270
|
+
<Text variant="caption" bold style={{ color: '#10B981', marginBottom: 4, fontSize: 10, lineHeight: 13 }}>ADVANCED ({rs.advanced_items.length})</Text>
|
|
271
|
+
{rs.advanced_items.map(item => {
|
|
272
|
+
const isMe = item.winning_player_id == player_id;
|
|
273
|
+
return (
|
|
274
|
+
<View key={item.calcutta_auction_item_id} variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 3 }}>
|
|
275
|
+
<Ionicons name="checkmark-circle" size={12} color="#10B981" />
|
|
276
|
+
<Text variant="caption" style={{ marginLeft: 6, flex: 1 }}>{item.item_name}</Text>
|
|
277
|
+
{isMe && <Text variant="caption" bold style={{ color: '#10B981', fontSize: 10, lineHeight: 13 }}>YOU</Text>}
|
|
278
|
+
</View>
|
|
279
|
+
);
|
|
280
|
+
})}
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
283
|
+
{rs.eliminated_items.length > 0 && (
|
|
284
|
+
<>
|
|
285
|
+
<Text variant="caption" bold style={{ color: theme.colors.status.error, marginTop: 8, marginBottom: 4, fontSize: 10, lineHeight: 13 }}>ELIMINATED ({rs.eliminated_items.length})</Text>
|
|
286
|
+
{rs.eliminated_items.map(item => {
|
|
287
|
+
const isMe = item.winning_player_id == player_id;
|
|
288
|
+
return (
|
|
289
|
+
<View key={item.calcutta_auction_item_id} variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 3, opacity: 0.6 }}>
|
|
290
|
+
<Ionicons name="close-circle" size={12} color={theme.colors.status.error} />
|
|
291
|
+
<Text variant="caption" style={{ marginLeft: 6, flex: 1 }}>{item.item_name}</Text>
|
|
292
|
+
{isMe && <Text variant="caption" bold style={{ color: theme.colors.status.error, fontSize: 10, lineHeight: 13 }}>YOU</Text>}
|
|
293
|
+
</View>
|
|
294
|
+
);
|
|
295
|
+
})}
|
|
296
|
+
</>
|
|
297
|
+
)}
|
|
298
|
+
{rs.prize_description && (
|
|
299
|
+
<View variant="transparent" style={{ marginTop: 8, flexDirection: 'row', alignItems: 'center' }}>
|
|
300
|
+
<Ionicons name="gift" size={14} color="#8B5CF6" />
|
|
301
|
+
<Text variant="caption" style={{ marginLeft: 6, color: '#8B5CF6' }}>{rs.prize_description}</Text>
|
|
302
|
+
</View>
|
|
303
|
+
)}
|
|
304
|
+
</View>
|
|
305
|
+
)}
|
|
306
|
+
</TouchableOpacity>
|
|
307
|
+
);
|
|
308
|
+
})}
|
|
309
|
+
</View>
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const renderLeaderboard = () => (
|
|
314
|
+
<View variant="transparent" style={{ paddingHorizontal: 16 }}>
|
|
315
|
+
<Text variant="caption" bold color="tertiary" style={styles.sectionLabel}>Leaderboard</Text>
|
|
316
|
+
{leaderboard.map((p, i) => {
|
|
317
|
+
const profile = enrichedPlayers[p.player_id];
|
|
318
|
+
const username = profile?.username || profile?.show_name || `Player`;
|
|
319
|
+
const profilePic = profile?.profile_pic;
|
|
320
|
+
const isMe = p.player_id == player_id;
|
|
321
|
+
const medals = ['๐ฅ', '๐ฅ', '๐ฅ'];
|
|
322
|
+
return (
|
|
323
|
+
<View key={p.calcutta_participant_id} variant="transparent" style={[styles.leaderRow, { borderColor: theme.colors.border.subtle }]}>
|
|
324
|
+
<Text style={{ width: 28, textAlign: 'center', fontSize: 14, lineHeight: 19 }}>{i < 3 ? medals[i] : `${i + 1}`}</Text>
|
|
325
|
+
{profilePic ? (
|
|
326
|
+
<Image source={{ uri: profilePic }} style={{ width: 28, height: 28, borderRadius: 14 }} />
|
|
327
|
+
) : (
|
|
328
|
+
<View variant="transparent" style={{ width: 28, height: 28, borderRadius: 14, backgroundColor: isMe ? '#10B98120' : theme.colors.primary.subtle, alignItems: 'center', justifyContent: 'center' }}>
|
|
329
|
+
<Text variant="caption" bold style={{ fontSize: 12, lineHeight: 16, color: isMe ? '#10B981' : theme.colors.primary.default }}>{username.charAt(0).toUpperCase()}</Text>
|
|
330
|
+
</View>
|
|
331
|
+
)}
|
|
332
|
+
<View variant="transparent" style={{ flex: 1, marginLeft: 8 }}>
|
|
333
|
+
<Text variant="body" bold={isMe} style={isMe ? { color: '#10B981' } : undefined}>{isMe ? 'You' : username}</Text>
|
|
334
|
+
<Text variant="caption" color="tertiary">{p.items_owned} item{p.items_owned !== 1 ? 's' : ''}</Text>
|
|
335
|
+
</View>
|
|
336
|
+
{Number(p.total_winnings) > 0 && (
|
|
337
|
+
<Text variant="caption" bold style={{ color: '#10B981' }}>{formatCurrency(p.total_winnings, marketType)}</Text>
|
|
338
|
+
)}
|
|
339
|
+
</View>
|
|
340
|
+
);
|
|
341
|
+
})}
|
|
342
|
+
</View>
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const renderAllItems = () => (
|
|
346
|
+
<View variant="transparent" style={{ paddingHorizontal: 16, paddingBottom: 40 }}>
|
|
347
|
+
<Text variant="caption" bold color="tertiary" style={styles.sectionLabel}>All Items ({items.length})</Text>
|
|
348
|
+
{filteredItems.map(item => {
|
|
349
|
+
const imgUrl = resolveItemImageUrl(item.item_image) || itemImages[item.item_id]?.url;
|
|
350
|
+
const owner = item.winning_player_id ? enrichedPlayers[item.winning_player_id] : undefined;
|
|
351
|
+
const ownerName = owner?.username || owner?.show_name;
|
|
352
|
+
const isMe = item.winning_player_id == player_id;
|
|
353
|
+
return (
|
|
354
|
+
<View key={item.calcutta_auction_item_id} variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderColor: theme.colors.border.subtle }}>
|
|
355
|
+
{imgUrl ? (
|
|
356
|
+
<Image source={{ uri: imgUrl }} style={{ width: 28, height: 28, borderRadius: 6 }} resizeMode="cover" />
|
|
357
|
+
) : (
|
|
358
|
+
<View variant="transparent" style={{ width: 28, height: 28, borderRadius: 6, backgroundColor: theme.colors.surface.elevated, alignItems: 'center', justifyContent: 'center' }}>
|
|
359
|
+
<Ionicons name="trophy-outline" size={14} color={theme.colors.text.tertiary} />
|
|
360
|
+
</View>
|
|
361
|
+
)}
|
|
362
|
+
<View variant="transparent" style={{ marginLeft: 8, flex: 1 }}>
|
|
363
|
+
<Text variant="body">{item.item_name}</Text>
|
|
364
|
+
<Text variant="caption" color="tertiary">
|
|
365
|
+
{ownerName ? (isMe ? 'You' : ownerName) : 'Unowned'}
|
|
366
|
+
{item.status === 'eliminated' ? ' ยท Eliminated' : item.status === 'sold' ? '' : ` ยท ${getStatusLabel(item.status)}`}
|
|
367
|
+
</Text>
|
|
368
|
+
</View>
|
|
369
|
+
{Number(item.winning_bid) > 0 && !isSweepstakes && (
|
|
370
|
+
<Text variant="caption" bold>{formatCurrency(item.winning_bid, marketType)}</Text>
|
|
371
|
+
)}
|
|
372
|
+
{isMe && <Text variant="caption" bold style={{ color: '#10B981', marginLeft: 6, fontSize: 10, lineHeight: 13 }}>YOU</Text>}
|
|
373
|
+
</View>
|
|
374
|
+
);
|
|
375
|
+
})}
|
|
376
|
+
</View>
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// โโโ RENDER โโโ
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<View variant="transparent" style={[styles.container, { backgroundColor: theme.colors.surface.base }]} onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}>
|
|
383
|
+
{isDesktop ? (
|
|
384
|
+
<ScrollView style={{ flex: 1, backgroundColor: theme.colors.surface.base }} contentContainerStyle={{ padding: 12, paddingBottom: 40, backgroundColor: theme.colors.surface.base }} keyboardShouldPersistTaps="handled">
|
|
385
|
+
{renderHeader()}
|
|
386
|
+
{renderMyItems()}
|
|
387
|
+
{renderPotKPIs()}
|
|
388
|
+
|
|
389
|
+
<View variant="transparent" style={{ flexDirection: 'row', gap: 12, marginTop: 12, paddingHorizontal: 4 }}>
|
|
390
|
+
{/* Left: Rounds */}
|
|
391
|
+
<View variant="transparent" style={[styles.panel, { flex: 3, backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
392
|
+
<ScrollView nestedScrollEnabled showsVerticalScrollIndicator={false}>
|
|
393
|
+
{renderRounds()}
|
|
394
|
+
</ScrollView>
|
|
395
|
+
</View>
|
|
396
|
+
|
|
397
|
+
{/* Right: Leaderboard */}
|
|
398
|
+
<View variant="transparent" style={[styles.panel, { flex: 2, backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
399
|
+
<ScrollView nestedScrollEnabled showsVerticalScrollIndicator={false}>
|
|
400
|
+
{renderLeaderboard()}
|
|
401
|
+
</ScrollView>
|
|
402
|
+
</View>
|
|
403
|
+
</View>
|
|
404
|
+
|
|
405
|
+
<View variant="transparent" style={[styles.panel, { marginTop: 12, marginHorizontal: 4, backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
406
|
+
<ScrollView nestedScrollEnabled showsVerticalScrollIndicator={false} style={{ maxHeight: 400 }}>
|
|
407
|
+
{renderAllItems()}
|
|
408
|
+
</ScrollView>
|
|
409
|
+
</View>
|
|
410
|
+
</ScrollView>
|
|
411
|
+
) : (
|
|
412
|
+
<ScrollView style={{ flex: 1, backgroundColor: theme.colors.surface.base }} contentContainerStyle={{ paddingBottom: Platform.OS === 'web' ? 300 : 40, backgroundColor: theme.colors.surface.base }} keyboardShouldPersistTaps="handled">
|
|
413
|
+
{renderHeader()}
|
|
414
|
+
{renderMyItems()}
|
|
415
|
+
{renderPotKPIs()}
|
|
416
|
+
{renderRounds()}
|
|
417
|
+
{renderLeaderboard()}
|
|
418
|
+
{renderAllItems()}
|
|
419
|
+
</ScrollView>
|
|
420
|
+
)}
|
|
421
|
+
</View>
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const styles = StyleSheet.create({
|
|
426
|
+
container: {
|
|
427
|
+
flex: 1,
|
|
428
|
+
},
|
|
429
|
+
sectionLabel: {
|
|
430
|
+
textTransform: 'uppercase',
|
|
431
|
+
letterSpacing: 1,
|
|
432
|
+
fontSize: 10,
|
|
433
|
+
lineHeight: 13,
|
|
434
|
+
marginBottom: 8,
|
|
435
|
+
marginTop: 16,
|
|
436
|
+
},
|
|
437
|
+
kpiCard: {
|
|
438
|
+
flex: 1,
|
|
439
|
+
borderRadius: 10,
|
|
440
|
+
padding: 10,
|
|
441
|
+
alignItems: 'center',
|
|
442
|
+
borderWidth: 1,
|
|
443
|
+
},
|
|
444
|
+
roundCard: {
|
|
445
|
+
borderRadius: 10,
|
|
446
|
+
borderWidth: 1,
|
|
447
|
+
padding: 12,
|
|
448
|
+
marginBottom: 8,
|
|
449
|
+
},
|
|
450
|
+
leaderRow: {
|
|
451
|
+
flexDirection: 'row',
|
|
452
|
+
alignItems: 'center',
|
|
453
|
+
paddingVertical: 8,
|
|
454
|
+
borderBottomWidth: 1,
|
|
455
|
+
},
|
|
456
|
+
panel: {
|
|
457
|
+
borderRadius: 12,
|
|
458
|
+
borderWidth: 1,
|
|
459
|
+
padding: 8,
|
|
460
|
+
},
|
|
461
|
+
});
|