@bettoredge/calcutta 0.4.0 โ†’ 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.
@@ -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
+ });