@bettoredge/calcutta 0.4.1 → 0.5.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.
- package/package.json +1 -1
- package/src/components/CalcuttaActionCard.tsx +31 -2
- package/src/components/CalcuttaAuction.tsx +26 -12
- package/src/components/CalcuttaDetail.tsx +261 -168
- package/src/components/CalcuttaEscrow.tsx +4 -4
- package/src/components/CalcuttaResults.tsx +470 -0
- package/src/components/CalcuttaWalkthrough.tsx +371 -0
- package/src/components/sealed/SealedBidAuction.tsx +140 -158
- package/src/index.ts +4 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
|
2
|
-
import { StyleSheet, TouchableOpacity, ActivityIndicator, ScrollView, Image, FlatList, TextInput, Platform } from 'react-native';
|
|
1
|
+
import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { StyleSheet, TouchableOpacity, ActivityIndicator, ScrollView, Image, FlatList, TextInput, Platform, useWindowDimensions, RefreshControl } from 'react-native';
|
|
3
3
|
import { View, Text, useTheme } from '@bettoredge/styles';
|
|
4
4
|
import { Ionicons } from '@expo/vector-icons';
|
|
5
5
|
import type { CalcuttaParticipantProps } from '@bettoredge/types';
|
|
@@ -15,6 +15,7 @@ import { CalcuttaCountdown } from './CalcuttaCountdown';
|
|
|
15
15
|
import { AuctionInfoChips } from './AuctionInfoChips';
|
|
16
16
|
import { EscrowWidget } from './EscrowWidget';
|
|
17
17
|
import { AuctionCountdownOverlay } from './AuctionCountdownOverlay';
|
|
18
|
+
import { CalcuttaWalkthrough } from './CalcuttaWalkthrough';
|
|
18
19
|
import { useCalcuttaSocket } from '../hooks/useCalcuttaSocket';
|
|
19
20
|
|
|
20
21
|
export interface CalcuttaDetailProps {
|
|
@@ -31,6 +32,10 @@ export interface CalcuttaDetailProps {
|
|
|
31
32
|
onManage?: () => void;
|
|
32
33
|
player_balance?: number;
|
|
33
34
|
onDepositFunds?: (amount: number) => void;
|
|
35
|
+
initialShowWalkthrough?: boolean;
|
|
36
|
+
onWalkthroughDismiss?: () => void;
|
|
37
|
+
onRefresh?: () => void | Promise<void>;
|
|
38
|
+
onAuctionStarted?: () => void;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
const getStatusConfig = (status: string) => {
|
|
@@ -54,12 +59,31 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
54
59
|
onManage,
|
|
55
60
|
player_balance,
|
|
56
61
|
onDepositFunds,
|
|
62
|
+
initialShowWalkthrough,
|
|
63
|
+
onWalkthroughDismiss,
|
|
57
64
|
access_token,
|
|
58
65
|
device_id,
|
|
59
66
|
player_username,
|
|
60
67
|
player_profile_pic,
|
|
68
|
+
onRefresh,
|
|
69
|
+
onAuctionStarted,
|
|
61
70
|
}) => {
|
|
62
71
|
const { theme } = useTheme();
|
|
72
|
+
const { width: windowWidth } = useWindowDimensions();
|
|
73
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
74
|
+
const measuredWidth = containerWidth || windowWidth;
|
|
75
|
+
const isDesktop = Platform.OS === 'web' && measuredWidth >= 700;
|
|
76
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
77
|
+
|
|
78
|
+
// Walkthrough state
|
|
79
|
+
const [showWalkthrough, setShowWalkthrough] = useState(false);
|
|
80
|
+
const walkthroughTriggered = useRef(false);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (initialShowWalkthrough && !walkthroughTriggered.current) {
|
|
83
|
+
walkthroughTriggered.current = true;
|
|
84
|
+
setShowWalkthrough(true);
|
|
85
|
+
}
|
|
86
|
+
}, [initialShowWalkthrough]);
|
|
63
87
|
|
|
64
88
|
// Countdown overlay state
|
|
65
89
|
const [showCountdown, setShowCountdown] = useState(false);
|
|
@@ -102,6 +126,14 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
102
126
|
const [leaving, setLeaving] = useState(false);
|
|
103
127
|
const [startingComp, setStartingComp] = useState(false);
|
|
104
128
|
const [itemSearch, setItemSearch] = useState('');
|
|
129
|
+
const [mobileListTab, setMobileListTab] = useState<'items' | 'players'>('items');
|
|
130
|
+
|
|
131
|
+
const handleRefresh = async () => {
|
|
132
|
+
setRefreshing(true);
|
|
133
|
+
try {
|
|
134
|
+
await Promise.all([refresh(), fetchEscrow(), onRefresh?.()]);
|
|
135
|
+
} catch {} finally { setRefreshing(false); }
|
|
136
|
+
};
|
|
105
137
|
|
|
106
138
|
const filteredItems = useMemo(() => {
|
|
107
139
|
if (competition?.auction_type !== 'sweepstakes' || !itemSearch.trim()) return items;
|
|
@@ -164,13 +196,124 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
164
196
|
...(showEscrow ? [{ key: 'escrow' }] : []),
|
|
165
197
|
{ key: 'stats' },
|
|
166
198
|
{ key: 'payouts' },
|
|
167
|
-
{ key: 'items-
|
|
168
|
-
{ key: 'items' },
|
|
169
|
-
{ key: 'participants-header' },
|
|
170
|
-
{ key: 'participants' },
|
|
199
|
+
{ key: 'items-players-tabbed' },
|
|
171
200
|
...(canLeave ? [{ key: 'leave' }] : []),
|
|
172
201
|
];
|
|
173
202
|
|
|
203
|
+
const renderItemsList = () => {
|
|
204
|
+
if (items.length === 0) {
|
|
205
|
+
return (
|
|
206
|
+
<View variant="transparent" style={{ padding: 20, alignItems: 'center' }}>
|
|
207
|
+
<Ionicons name="list-outline" size={32} color={theme.colors.text.tertiary} />
|
|
208
|
+
<Text variant="caption" color="tertiary" style={{ marginTop: 8 }}>No items added yet.</Text>
|
|
209
|
+
</View>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const displayItems = isSweepstakes ? filteredItems : items;
|
|
214
|
+
return (
|
|
215
|
+
<View variant="transparent" style={{ paddingHorizontal: 16, paddingBottom: 8 }}>
|
|
216
|
+
{isSweepstakes && (
|
|
217
|
+
<TextInput
|
|
218
|
+
style={{
|
|
219
|
+
height: 36, borderRadius: 8, borderWidth: 1,
|
|
220
|
+
borderColor: theme.colors.border.subtle, backgroundColor: theme.colors.surface.input,
|
|
221
|
+
color: theme.colors.text.primary, paddingHorizontal: 12, fontSize: 14, lineHeight: 19, marginBottom: 8,
|
|
222
|
+
}}
|
|
223
|
+
placeholder="Search by team or owner..."
|
|
224
|
+
placeholderTextColor={theme.colors.text.tertiary}
|
|
225
|
+
value={itemSearch}
|
|
226
|
+
onChangeText={setItemSearch}
|
|
227
|
+
autoCapitalize="none"
|
|
228
|
+
autoCorrect={false}
|
|
229
|
+
onFocus={(e: any) => {
|
|
230
|
+
if (Platform.OS === 'web' && e?.target?.scrollIntoView) {
|
|
231
|
+
setTimeout(() => e.target.scrollIntoView({ behavior: 'smooth', block: 'center' }), 300);
|
|
232
|
+
}
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
{displayItems.map(item => {
|
|
237
|
+
const imgUrl = resolveItemImageUrl(item.item_image) || itemImages[item.item_id]?.url;
|
|
238
|
+
const owner = item.winning_player_id ? enrichedPlayers[item.winning_player_id] : undefined;
|
|
239
|
+
const ownerName = owner?.username || owner?.show_name;
|
|
240
|
+
const isMe = item.winning_player_id == player_id;
|
|
241
|
+
return (
|
|
242
|
+
<View key={item.calcutta_auction_item_id} variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderColor: theme.colors.border.subtle }}>
|
|
243
|
+
{imgUrl ? (
|
|
244
|
+
<Image source={{ uri: imgUrl }} style={{ width: 36, height: 36, borderRadius: 8 }} resizeMode="cover" />
|
|
245
|
+
) : (
|
|
246
|
+
<View variant="transparent" style={{ width: 36, height: 36, borderRadius: 8, backgroundColor: theme.colors.surface.elevated, alignItems: 'center', justifyContent: 'center' }}>
|
|
247
|
+
<Ionicons name="trophy-outline" size={16} color={theme.colors.text.tertiary} />
|
|
248
|
+
</View>
|
|
249
|
+
)}
|
|
250
|
+
<View variant="transparent" style={{ marginLeft: 10, flex: 1 }}>
|
|
251
|
+
<Text variant="body">{item.item_name}</Text>
|
|
252
|
+
{isSweepstakes && ownerName ? (
|
|
253
|
+
<Text variant="caption" color="tertiary">{isMe ? 'You' : ownerName}</Text>
|
|
254
|
+
) : isSweepstakes ? (
|
|
255
|
+
<Text variant="caption" color="tertiary">Available</Text>
|
|
256
|
+
) : null}
|
|
257
|
+
</View>
|
|
258
|
+
{item.seed != null && <Text variant="caption" color="tertiary">#{item.seed}</Text>}
|
|
259
|
+
{isMe && <Text variant="caption" bold style={{ color: theme.colors.primary.default, marginLeft: 8 }}>YOU</Text>}
|
|
260
|
+
</View>
|
|
261
|
+
);
|
|
262
|
+
})}
|
|
263
|
+
</View>
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const renderParticipantsList = () => {
|
|
268
|
+
if (participants.length === 0) {
|
|
269
|
+
return (
|
|
270
|
+
<View variant="transparent" style={{ padding: 20, alignItems: 'center' }}>
|
|
271
|
+
<Ionicons name="people-outline" size={32} color={theme.colors.text.tertiary} />
|
|
272
|
+
<Text variant="caption" color="tertiary" style={{ marginTop: 8 }}>No players yet. Share the code to invite friends!</Text>
|
|
273
|
+
</View>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return (
|
|
277
|
+
<View variant="transparent" style={{ paddingHorizontal: 16, paddingBottom: 8 }}>
|
|
278
|
+
{participants.map((p, i) => {
|
|
279
|
+
const enriched = enrichedPlayers[p.player_id];
|
|
280
|
+
const username = enriched?.username || enriched?.show_name || `Player ${p.player_id.slice(0, 6)}`;
|
|
281
|
+
const profilePic = enriched?.profile_pic;
|
|
282
|
+
const isOnline = onlinePlayerIds.has(p.player_id);
|
|
283
|
+
return (
|
|
284
|
+
<View key={p.calcutta_participant_id} variant="transparent" style={[styles.participantRow, i < participants.length - 1 && { borderBottomWidth: 1, borderColor: theme.colors.border.subtle }]}>
|
|
285
|
+
<View variant="transparent" style={{ position: 'relative' }}>
|
|
286
|
+
{profilePic ? (
|
|
287
|
+
<Image source={{ uri: profilePic }} style={[styles.avatarCircle, { overflow: 'hidden' }]} />
|
|
288
|
+
) : (
|
|
289
|
+
<View variant="transparent" style={[styles.avatarCircle, { backgroundColor: theme.colors.primary.subtle }]}>
|
|
290
|
+
<Text variant="caption" bold style={{ color: theme.colors.primary.default }}>
|
|
291
|
+
{username.charAt(0).toUpperCase()}
|
|
292
|
+
</Text>
|
|
293
|
+
</View>
|
|
294
|
+
)}
|
|
295
|
+
{isOnline && (
|
|
296
|
+
<View variant="transparent" style={{ position: 'absolute', bottom: 0, right: 0, width: 10, height: 10, borderRadius: 5, backgroundColor: '#10B981', borderWidth: 2, borderColor: theme.colors.surface.base }} />
|
|
297
|
+
)}
|
|
298
|
+
</View>
|
|
299
|
+
<View variant="transparent" style={{ flex: 1 }}>
|
|
300
|
+
<Text variant="body">{username}</Text>
|
|
301
|
+
<Text variant="caption" color="tertiary">
|
|
302
|
+
{p.items_owned > 0 ? `${p.items_owned} items \u00B7 ${formatCurrency(p.total_spent, competition.market_type)} spent` : 'Joined'}
|
|
303
|
+
</Text>
|
|
304
|
+
</View>
|
|
305
|
+
{p.player_id == player_id && (
|
|
306
|
+
<View variant="transparent" style={[styles.youBadge, { backgroundColor: theme.colors.primary.subtle }]}>
|
|
307
|
+
<Text variant="caption" bold style={{ color: theme.colors.primary.default, fontSize: 10, lineHeight: 13 }}>YOU</Text>
|
|
308
|
+
</View>
|
|
309
|
+
)}
|
|
310
|
+
</View>
|
|
311
|
+
);
|
|
312
|
+
})}
|
|
313
|
+
</View>
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
174
317
|
const renderSection = ({ item }: { item: { key: string } }) => {
|
|
175
318
|
switch (item.key) {
|
|
176
319
|
case 'hero':
|
|
@@ -194,6 +337,14 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
194
337
|
{isSweepstakes ? 'Sweepstakes' : competition.auction_type === 'live' ? 'Live Auction' : 'Sealed Bid'}
|
|
195
338
|
</Text>
|
|
196
339
|
<View variant="transparent" style={{ flex: 1 }} />
|
|
340
|
+
<TouchableOpacity
|
|
341
|
+
onPress={() => setShowWalkthrough(true)}
|
|
342
|
+
style={{ marginRight: 8, padding: 4 }}
|
|
343
|
+
activeOpacity={0.7}
|
|
344
|
+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
345
|
+
>
|
|
346
|
+
<Ionicons name="help-circle-outline" size={20} color={theme.colors.text.tertiary} />
|
|
347
|
+
</TouchableOpacity>
|
|
197
348
|
{isAdmin && onManage && (
|
|
198
349
|
<TouchableOpacity
|
|
199
350
|
onPress={onManage}
|
|
@@ -275,6 +426,7 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
275
426
|
hasJoined={hasJoined}
|
|
276
427
|
escrowBalance={Number(escrow?.escrow_balance ?? 0)}
|
|
277
428
|
isAdmin={isAdmin}
|
|
429
|
+
participantCount={participants.length}
|
|
278
430
|
onJoin={onJoin ? async () => {
|
|
279
431
|
setJoining(true);
|
|
280
432
|
try { await onJoin(); await refresh(); fetchEscrow(); } catch {}
|
|
@@ -406,148 +558,43 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
406
558
|
</View>
|
|
407
559
|
);
|
|
408
560
|
|
|
409
|
-
case 'items-header':
|
|
410
|
-
if (items.length === 0) return null;
|
|
411
|
-
return (
|
|
412
|
-
<View variant="transparent" style={[styles.section, { borderColor: theme.colors.border.subtle, paddingBottom: 0 }]}>
|
|
413
|
-
<Text variant="body" bold style={styles.sectionTitle}>{isSweepstakes ? 'Teams' : 'Auction Items'} ({items.length})</Text>
|
|
414
|
-
</View>
|
|
415
|
-
);
|
|
416
|
-
|
|
417
561
|
case 'items':
|
|
418
|
-
|
|
419
|
-
if (isSweepstakes) {
|
|
420
|
-
return (
|
|
421
|
-
<View variant="transparent" style={{ paddingHorizontal: 16, paddingBottom: 8 }}>
|
|
422
|
-
<TextInput
|
|
423
|
-
style={{
|
|
424
|
-
height: 36,
|
|
425
|
-
borderRadius: 8,
|
|
426
|
-
borderWidth: 1,
|
|
427
|
-
borderColor: theme.colors.border.subtle,
|
|
428
|
-
backgroundColor: theme.colors.surface.input,
|
|
429
|
-
color: theme.colors.text.primary,
|
|
430
|
-
paddingHorizontal: 12,
|
|
431
|
-
fontSize: 14,
|
|
432
|
-
lineHeight: 19,
|
|
433
|
-
marginBottom: 8,
|
|
434
|
-
}}
|
|
435
|
-
placeholder="Search by team or owner..."
|
|
436
|
-
placeholderTextColor={theme.colors.text.tertiary}
|
|
437
|
-
value={itemSearch}
|
|
438
|
-
onChangeText={setItemSearch}
|
|
439
|
-
autoCapitalize="none"
|
|
440
|
-
autoCorrect={false}
|
|
441
|
-
onFocus={(e: any) => {
|
|
442
|
-
if (Platform.OS === 'web' && e?.target?.scrollIntoView) {
|
|
443
|
-
setTimeout(() => e.target.scrollIntoView({ behavior: 'smooth', block: 'center' }), 300);
|
|
444
|
-
}
|
|
445
|
-
}}
|
|
446
|
-
/>
|
|
447
|
-
{filteredItems.map(item => {
|
|
448
|
-
const imgUrl = resolveItemImageUrl(item.item_image) || itemImages[item.item_id]?.url;
|
|
449
|
-
const owner = item.winning_player_id ? enrichedPlayers[item.winning_player_id] : undefined;
|
|
450
|
-
const ownerName = owner?.username || owner?.show_name;
|
|
451
|
-
const isMe = item.winning_player_id == player_id;
|
|
452
|
-
return (
|
|
453
|
-
<View key={item.calcutta_auction_item_id} variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderColor: theme.colors.border.subtle }}>
|
|
454
|
-
{imgUrl ? (
|
|
455
|
-
<Image source={{ uri: imgUrl }} style={{ width: 28, height: 28, borderRadius: 6 }} resizeMode="cover" />
|
|
456
|
-
) : (
|
|
457
|
-
<View variant="transparent" style={{ width: 28, height: 28, borderRadius: 6, backgroundColor: theme.colors.surface.elevated, alignItems: 'center', justifyContent: 'center' }}>
|
|
458
|
-
<Ionicons name="trophy-outline" size={14} color={theme.colors.text.tertiary} />
|
|
459
|
-
</View>
|
|
460
|
-
)}
|
|
461
|
-
<View variant="transparent" style={{ marginLeft: 8, flex: 1 }}>
|
|
462
|
-
<Text variant="body">{item.item_name}</Text>
|
|
463
|
-
{ownerName ? (
|
|
464
|
-
<Text variant="caption" color="tertiary">{isMe ? 'You' : ownerName}</Text>
|
|
465
|
-
) : (
|
|
466
|
-
<Text variant="caption" color="tertiary">Available</Text>
|
|
467
|
-
)}
|
|
468
|
-
</View>
|
|
469
|
-
{item.seed != null && <Text variant="caption" color="tertiary">#{item.seed}</Text>}
|
|
470
|
-
{isMe && <Text variant="caption" bold style={{ color: theme.colors.primary.default, marginLeft: 8 }}>YOU</Text>}
|
|
471
|
-
</View>
|
|
472
|
-
);
|
|
473
|
-
})}
|
|
474
|
-
</View>
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
return (
|
|
478
|
-
<View variant="transparent" style={{ paddingHorizontal: 16 }}>
|
|
479
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
480
|
-
{items.map(item => (
|
|
481
|
-
<View key={item.calcutta_auction_item_id} variant="transparent" style={[styles.itemChip, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
482
|
-
<Text variant="caption" bold numberOfLines={1}>{item.item_name}</Text>
|
|
483
|
-
{item.seed != null && <Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>#{item.seed}</Text>}
|
|
484
|
-
</View>
|
|
485
|
-
))}
|
|
486
|
-
</ScrollView>
|
|
487
|
-
</View>
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
case 'participants-header':
|
|
491
|
-
return (
|
|
492
|
-
<View variant="transparent" style={[styles.section, { borderColor: theme.colors.border.subtle, paddingBottom: 0 }]}>
|
|
493
|
-
<View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 10 }}>
|
|
494
|
-
<Text variant="body" bold>Players ({participants.length})</Text>
|
|
495
|
-
{onlinePlayers.length > 0 && (
|
|
496
|
-
<View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', marginLeft: 10, backgroundColor: '#10B98115', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 10 }}>
|
|
497
|
-
<View variant="transparent" style={{ width: 6, height: 6, borderRadius: 3, backgroundColor: '#10B981', marginRight: 4 }} />
|
|
498
|
-
<Text variant="caption" bold style={{ color: '#10B981' }}>{onlinePlayers.length} online</Text>
|
|
499
|
-
</View>
|
|
500
|
-
)}
|
|
501
|
-
</View>
|
|
502
|
-
</View>
|
|
503
|
-
);
|
|
562
|
+
return renderItemsList();
|
|
504
563
|
|
|
505
564
|
case 'participants':
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
<Ionicons name="people-outline" size={32} color={theme.colors.text.tertiary} />
|
|
510
|
-
<Text variant="caption" color="tertiary" style={{ marginTop: 8 }}>No players yet. Share the code to invite friends!</Text>
|
|
511
|
-
</View>
|
|
512
|
-
);
|
|
513
|
-
}
|
|
565
|
+
return renderParticipantsList();
|
|
566
|
+
|
|
567
|
+
case 'items-players-tabbed':
|
|
514
568
|
return (
|
|
515
|
-
<View variant="transparent" style={{
|
|
516
|
-
{
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
<
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
<Text variant="
|
|
539
|
-
<Text variant="caption" color="tertiary">
|
|
540
|
-
{p.items_owned > 0 ? `${p.items_owned} items \u00B7 ${formatCurrency(p.total_spent, competition.market_type)} spent` : 'Joined'}
|
|
541
|
-
</Text>
|
|
569
|
+
<View variant="transparent" style={[styles.section, { borderColor: theme.colors.border.subtle }]}>
|
|
570
|
+
{/* Tab toggle */}
|
|
571
|
+
<View variant="transparent" style={[styles.listTabBar, { borderColor: theme.colors.border.subtle }]}>
|
|
572
|
+
<TouchableOpacity
|
|
573
|
+
style={[styles.listTab, mobileListTab === 'items' && { borderBottomWidth: 2, borderBottomColor: theme.colors.primary.default }]}
|
|
574
|
+
onPress={() => setMobileListTab('items')}
|
|
575
|
+
>
|
|
576
|
+
<Ionicons name="list-outline" size={16} color={mobileListTab === 'items' ? theme.colors.primary.default : theme.colors.text.tertiary} style={{ marginRight: 6 }} />
|
|
577
|
+
<Text variant="body" bold style={{ color: mobileListTab === 'items' ? theme.colors.primary.default : theme.colors.text.tertiary }}>
|
|
578
|
+
{isSweepstakes ? 'Teams' : 'Items'} ({items.length})
|
|
579
|
+
</Text>
|
|
580
|
+
</TouchableOpacity>
|
|
581
|
+
<TouchableOpacity
|
|
582
|
+
style={[styles.listTab, mobileListTab === 'players' && { borderBottomWidth: 2, borderBottomColor: theme.colors.primary.default }]}
|
|
583
|
+
onPress={() => setMobileListTab('players')}
|
|
584
|
+
>
|
|
585
|
+
<Ionicons name="people-outline" size={16} color={mobileListTab === 'players' ? theme.colors.primary.default : theme.colors.text.tertiary} style={{ marginRight: 6 }} />
|
|
586
|
+
<Text variant="body" bold style={{ color: mobileListTab === 'players' ? theme.colors.primary.default : theme.colors.text.tertiary }}>
|
|
587
|
+
Players ({participants.length})
|
|
588
|
+
</Text>
|
|
589
|
+
{onlinePlayers.length > 0 && (
|
|
590
|
+
<View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', marginLeft: 8, backgroundColor: '#10B98115', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 8 }}>
|
|
591
|
+
<View variant="transparent" style={{ width: 5, height: 5, borderRadius: 2.5, backgroundColor: '#10B981', marginRight: 3 }} />
|
|
592
|
+
<Text variant="caption" bold style={{ color: '#10B981', fontSize: 10, lineHeight: 13 }}>{onlinePlayers.length}</Text>
|
|
542
593
|
</View>
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
)}
|
|
548
|
-
</View>
|
|
549
|
-
);
|
|
550
|
-
})}
|
|
594
|
+
)}
|
|
595
|
+
</TouchableOpacity>
|
|
596
|
+
</View>
|
|
597
|
+
{mobileListTab === 'items' ? renderItemsList() : renderParticipantsList()}
|
|
551
598
|
</View>
|
|
552
599
|
);
|
|
553
600
|
|
|
@@ -556,32 +603,65 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
556
603
|
}
|
|
557
604
|
};
|
|
558
605
|
|
|
559
|
-
const socketColor = socketState === 'authenticated' ? '#10B981'
|
|
560
|
-
: socketState === 'connected' ? '#F59E0B'
|
|
561
|
-
: socketState === 'connecting' ? '#3B82F6'
|
|
562
|
-
: '#EF4444';
|
|
563
|
-
|
|
564
606
|
return (
|
|
565
|
-
<View variant="
|
|
566
|
-
{
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
607
|
+
<View variant="transparent" style={[styles.container, { backgroundColor: theme.colors.surface.base }]} onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}>
|
|
608
|
+
{isDesktop ? (
|
|
609
|
+
<ScrollView style={{ flex: 1, backgroundColor: theme.colors.surface.base }} contentContainerStyle={{ padding: 12, paddingBottom: 40, backgroundColor: theme.colors.surface.base }} keyboardShouldPersistTaps="handled" refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}>
|
|
610
|
+
{/* Row 1: Hero + Info/Action left, Items right */}
|
|
611
|
+
<View variant="transparent" style={{ flexDirection: 'row', gap: 12 }}>
|
|
612
|
+
<View variant="transparent" style={{ flex: 3 }}>
|
|
613
|
+
{renderSection({ item: { key: 'hero' } })}
|
|
614
|
+
{renderSection({ item: { key: 'info' } })}
|
|
615
|
+
{renderSection({ item: { key: 'action-card' } })}
|
|
616
|
+
{showEscrow && renderSection({ item: { key: 'escrow' } })}
|
|
617
|
+
</View>
|
|
618
|
+
<View variant="transparent" style={[styles.desktopPanel, { flex: 2, backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
619
|
+
<Text variant="caption" bold color="tertiary" style={styles.desktopPanelTitle}>
|
|
620
|
+
{isSweepstakes ? 'Teams' : 'Auction Items'} ({items.length})
|
|
621
|
+
</Text>
|
|
622
|
+
<ScrollView nestedScrollEnabled showsVerticalScrollIndicator={false}>
|
|
623
|
+
{renderSection({ item: { key: 'items' } })}
|
|
624
|
+
</ScrollView>
|
|
625
|
+
</View>
|
|
626
|
+
</View>
|
|
574
627
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
628
|
+
{/* Row 2: Stats + Payouts + Participants */}
|
|
629
|
+
<View variant="transparent" style={{ flexDirection: 'row', gap: 12, marginTop: 12 }}>
|
|
630
|
+
<View variant="transparent" style={{ flex: 1 }}>
|
|
631
|
+
{renderSection({ item: { key: 'stats' } })}
|
|
632
|
+
</View>
|
|
633
|
+
</View>
|
|
634
|
+
|
|
635
|
+
<View variant="transparent" style={{ flexDirection: 'row', gap: 12, marginTop: 12 }}>
|
|
636
|
+
<View variant="transparent" style={[styles.desktopPanel, { flex: 1, backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
637
|
+
<Text variant="caption" bold color="tertiary" style={styles.desktopPanelTitle}>Payouts</Text>
|
|
638
|
+
{renderSection({ item: { key: 'payouts' } })}
|
|
639
|
+
</View>
|
|
640
|
+
<View variant="transparent" style={[styles.desktopPanel, { flex: 1, backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
|
|
641
|
+
<Text variant="caption" bold color="tertiary" style={styles.desktopPanelTitle}>
|
|
642
|
+
Players ({participants.length})
|
|
643
|
+
</Text>
|
|
644
|
+
<ScrollView nestedScrollEnabled showsVerticalScrollIndicator={false} style={{ maxHeight: 300 }}>
|
|
645
|
+
{renderSection({ item: { key: 'participants' } })}
|
|
646
|
+
</ScrollView>
|
|
647
|
+
</View>
|
|
648
|
+
</View>
|
|
649
|
+
|
|
650
|
+
{canLeave && renderSection({ item: { key: 'leave' } })}
|
|
651
|
+
</ScrollView>
|
|
652
|
+
) : (
|
|
653
|
+
<FlatList
|
|
654
|
+
data={sections}
|
|
655
|
+
keyExtractor={(item) => item.key}
|
|
656
|
+
renderItem={renderSection}
|
|
657
|
+
extraData={`${escrowExpanded}-${onlinePlayers.length}`}
|
|
658
|
+
showsVerticalScrollIndicator={false}
|
|
659
|
+
style={{ backgroundColor: theme.colors.surface.base }}
|
|
660
|
+
contentContainerStyle={{ paddingBottom: Platform.OS === 'web' ? 300 : 40 }}
|
|
661
|
+
keyboardShouldPersistTaps="handled"
|
|
662
|
+
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}
|
|
663
|
+
/>
|
|
664
|
+
)}
|
|
585
665
|
|
|
586
666
|
{/* Auction starting countdown overlay */}
|
|
587
667
|
<AuctionCountdownOverlay
|
|
@@ -592,8 +672,18 @@ export const CalcuttaDetail: React.FC<CalcuttaDetailProps> = ({
|
|
|
592
672
|
onComplete={() => {
|
|
593
673
|
setShowCountdown(false);
|
|
594
674
|
refresh();
|
|
675
|
+
onAuctionStarted?.();
|
|
595
676
|
}}
|
|
596
677
|
/>
|
|
678
|
+
|
|
679
|
+
{/* Walkthrough */}
|
|
680
|
+
{competition && (
|
|
681
|
+
<CalcuttaWalkthrough
|
|
682
|
+
visible={showWalkthrough}
|
|
683
|
+
onClose={() => { setShowWalkthrough(false); onWalkthroughDismiss?.(); }}
|
|
684
|
+
auction_type={competition.auction_type as any}
|
|
685
|
+
/>
|
|
686
|
+
)}
|
|
597
687
|
</View>
|
|
598
688
|
);
|
|
599
689
|
};
|
|
@@ -617,10 +707,13 @@ const styles = StyleSheet.create({
|
|
|
617
707
|
section: { padding: 16, borderTopWidth: 1 },
|
|
618
708
|
sectionTitle: { marginBottom: 10 },
|
|
619
709
|
payoutRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10 },
|
|
620
|
-
itemChip: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 8, borderWidth: 1, marginRight: 8, minWidth: 80, alignItems: 'center' },
|
|
621
710
|
participantRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10 },
|
|
622
711
|
avatarCircle: { width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center', marginRight: 10 },
|
|
623
712
|
youBadge: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 8 },
|
|
624
713
|
assignedItemCard: { flexDirection: 'row', alignItems: 'center', padding: 12, borderRadius: 10, borderWidth: 1 },
|
|
625
714
|
assignedItemImage: { width: 44, height: 44, borderRadius: 8 },
|
|
715
|
+
listTabBar: { flexDirection: 'row', borderBottomWidth: 1, marginBottom: 8 },
|
|
716
|
+
listTab: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 10 },
|
|
717
|
+
desktopPanel: { borderRadius: 12, borderWidth: 1, padding: 14 },
|
|
718
|
+
desktopPanelTitle: { textTransform: 'uppercase', letterSpacing: 1, fontSize: 10, lineHeight: 13, marginBottom: 8 },
|
|
626
719
|
});
|
|
@@ -226,7 +226,7 @@ export const CalcuttaEscrow: React.FC<CalcuttaEscrowComponentProps> = ({
|
|
|
226
226
|
activeOpacity={0.7}
|
|
227
227
|
>
|
|
228
228
|
<Ionicons
|
|
229
|
-
name="arrow-
|
|
229
|
+
name="arrow-up-outline"
|
|
230
230
|
size={18}
|
|
231
231
|
color={action === 'transfer_in' ? '#FFFFFF' : theme.colors.text.secondary}
|
|
232
232
|
/>
|
|
@@ -250,7 +250,7 @@ export const CalcuttaEscrow: React.FC<CalcuttaEscrowComponentProps> = ({
|
|
|
250
250
|
activeOpacity={0.7}
|
|
251
251
|
>
|
|
252
252
|
<Ionicons
|
|
253
|
-
name="arrow-
|
|
253
|
+
name="arrow-down-outline"
|
|
254
254
|
size={18}
|
|
255
255
|
color={action === 'transfer_out' ? '#FFFFFF' : theme.colors.text.secondary}
|
|
256
256
|
/>
|
|
@@ -307,7 +307,7 @@ export const CalcuttaEscrow: React.FC<CalcuttaEscrowComponentProps> = ({
|
|
|
307
307
|
? theme.colors.status.error
|
|
308
308
|
: (loading || isInvalidAmount)
|
|
309
309
|
? theme.colors.surface.elevated
|
|
310
|
-
: theme.colors.
|
|
310
|
+
: theme.colors.status.success,
|
|
311
311
|
}]}
|
|
312
312
|
onPress={handleAction}
|
|
313
313
|
disabled={loading || (isInvalidAmount && !(insufficientBalance && onDepositFunds))}
|
|
@@ -318,7 +318,7 @@ export const CalcuttaEscrow: React.FC<CalcuttaEscrowComponentProps> = ({
|
|
|
318
318
|
) : (
|
|
319
319
|
<>
|
|
320
320
|
<Ionicons
|
|
321
|
-
name={action === 'transfer_in' ? 'arrow-
|
|
321
|
+
name={action === 'transfer_in' ? 'arrow-up-circle' : 'arrow-down-circle'}
|
|
322
322
|
size={20}
|
|
323
323
|
color={isInvalidAmount ? theme.colors.text.tertiary : '#FFFFFF'}
|
|
324
324
|
/>
|