@bettoredge/calcutta 0.3.1 → 0.4.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 +3 -2
- package/src/components/AuctionCompleteOverlay.tsx +306 -0
- package/src/components/AuctionCountdownOverlay.tsx +178 -0
- package/src/components/AuctionInfoChips.tsx +105 -0
- package/src/components/AuctionPausedOverlay.tsx +154 -0
- package/src/components/CalcuttaActionCard.tsx +291 -0
- package/src/components/CalcuttaAuction.tsx +677 -281
- package/src/components/CalcuttaAuctionItem.tsx +195 -106
- package/src/components/CalcuttaBidInput.tsx +130 -139
- package/src/components/CalcuttaCountdown.tsx +183 -0
- package/src/components/CalcuttaDetail.tsx +211 -144
- package/src/components/CalcuttaEscrow.tsx +49 -13
- package/src/components/CalcuttaLeaderboard.tsx +2 -2
- package/src/components/EscrowWidget.tsx +176 -0
- package/src/components/ItemSoldCelebration.tsx +288 -0
- package/src/components/SweepstakesReveal.tsx +1 -1
- package/src/components/sealed/SealedBidAuction.tsx +7 -9
- package/src/components/sealed/SealedBidHeader.tsx +3 -3
- package/src/components/sealed/SealedBidItemCard.tsx +9 -9
- package/src/components/sealed/SealedBidItemsTab.tsx +2 -1
- package/src/components/sealed/SealedBidMyBidsTab.tsx +3 -3
- package/src/components/sealed/SealedBidPlayersTab.tsx +2 -2
- package/src/components/sealed/SealedBidStatusBar.tsx +1 -1
- package/src/components/sealed/SealedBidTabBar.tsx +2 -0
- package/src/hooks/useCalcuttaAuction.ts +16 -2
- package/src/hooks/useCalcuttaEscrow.ts +5 -1
- package/src/hooks/useCalcuttaSocket.ts +80 -82
- package/src/index.ts +18 -0
|
@@ -227,7 +227,7 @@ export const SealedBidMyBidsTab: React.FC<SealedBidMyBidsTabProps> = ({
|
|
|
227
227
|
<Text variant="body" bold numberOfLines={1}>{item.item_name}</Text>
|
|
228
228
|
<View variant="transparent" style={{ flexDirection: 'row', alignItems: 'center', marginTop: 2 }}>
|
|
229
229
|
{isEliminated && (
|
|
230
|
-
<Text variant="caption" bold style={{ color: theme.colors.status.error, fontSize: 10, marginRight: 6 }}>ELIMINATED</Text>
|
|
230
|
+
<Text variant="caption" bold style={{ color: theme.colors.status.error, fontSize: 10, lineHeight: 13, marginRight: 6 }}>ELIMINATED</Text>
|
|
231
231
|
)}
|
|
232
232
|
{item.seed != null && (
|
|
233
233
|
<Text variant="caption" color="tertiary">Seed #{item.seed}</Text>
|
|
@@ -235,7 +235,7 @@ export const SealedBidMyBidsTab: React.FC<SealedBidMyBidsTabProps> = ({
|
|
|
235
235
|
</View>
|
|
236
236
|
</View>
|
|
237
237
|
<View variant="transparent" style={{ alignItems: 'flex-end' }}>
|
|
238
|
-
<Text variant="caption" color="tertiary" style={{ fontSize: 10 }}>ROI</Text>
|
|
238
|
+
<Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>ROI</Text>
|
|
239
239
|
<Text variant="body" bold style={{
|
|
240
240
|
color: itemROI > 0
|
|
241
241
|
? theme.colors.status.success
|
|
@@ -358,7 +358,7 @@ export const SealedBidMyBidsTab: React.FC<SealedBidMyBidsTabProps> = ({
|
|
|
358
358
|
size={8}
|
|
359
359
|
color={getBidStatusColor(row.bid.bid_status)}
|
|
360
360
|
/>
|
|
361
|
-
<Text variant="caption" style={{ color: getBidStatusColor(row.bid.bid_status), marginLeft: 4, fontSize: 11 }}>
|
|
361
|
+
<Text variant="caption" style={{ color: getBidStatusColor(row.bid.bid_status), marginLeft: 4, fontSize: 11, lineHeight: 15 }}>
|
|
362
362
|
{getStatusLabel(row.bid.bid_status)}
|
|
363
363
|
</Text>
|
|
364
364
|
</View>
|
|
@@ -131,7 +131,7 @@ export const SealedBidPlayersTab: React.FC<SealedBidPlayersTabProps> = ({
|
|
|
131
131
|
<Text variant="body" bold={isMe} numberOfLines={1}>{username}</Text>
|
|
132
132
|
{isMe && (
|
|
133
133
|
<View variant="transparent" style={[styles.youBadge, { backgroundColor: theme.colors.primary.subtle }]}>
|
|
134
|
-
<Text variant="caption" bold style={{ color: theme.colors.primary.default, fontSize: 9 }}>YOU</Text>
|
|
134
|
+
<Text variant="caption" bold style={{ color: theme.colors.primary.default, fontSize: 9, lineHeight: 12 }}>YOU</Text>
|
|
135
135
|
</View>
|
|
136
136
|
)}
|
|
137
137
|
</View>
|
|
@@ -147,7 +147,7 @@ export const SealedBidPlayersTab: React.FC<SealedBidPlayersTabProps> = ({
|
|
|
147
147
|
{/* Winnings in results phase */}
|
|
148
148
|
{isResultsPhase && participant.total_winnings > 0 && (
|
|
149
149
|
<View variant="transparent" style={{ alignItems: 'flex-end' }}>
|
|
150
|
-
<Text variant="caption" color="tertiary" style={{ fontSize: 10 }}>Won</Text>
|
|
150
|
+
<Text variant="caption" color="tertiary" style={{ fontSize: 10, lineHeight: 13 }}>Won</Text>
|
|
151
151
|
<Text variant="body" bold style={{ color: theme.colors.status.success }}>
|
|
152
152
|
{formatCurrency(participant.total_winnings, market_type)}
|
|
153
153
|
</Text>
|
|
@@ -51,7 +51,7 @@ export const SealedBidStatusBar: React.FC<SealedBidStatusBarProps> = ({
|
|
|
51
51
|
const timerTarget = lifecycleState === 'auctioning'
|
|
52
52
|
? competition.auction_end_datetime
|
|
53
53
|
: lifecycleState === 'scheduled'
|
|
54
|
-
? competition.
|
|
54
|
+
? competition.scheduled_datetime
|
|
55
55
|
: undefined;
|
|
56
56
|
|
|
57
57
|
useEffect(() => {
|
|
@@ -97,6 +97,7 @@ export const SealedBidTabBar: React.FC<SealedBidTabBarProps> = ({
|
|
|
97
97
|
color: isActive ? theme.colors.primary.default : theme.colors.text.tertiary,
|
|
98
98
|
marginLeft: 4,
|
|
99
99
|
fontSize: 11,
|
|
100
|
+
lineHeight: 15,
|
|
100
101
|
}}
|
|
101
102
|
>
|
|
102
103
|
{tab.label}
|
|
@@ -118,6 +119,7 @@ export const SealedBidTabBar: React.FC<SealedBidTabBarProps> = ({
|
|
|
118
119
|
style={{
|
|
119
120
|
color: isActive ? '#FFFFFF' : theme.colors.text.tertiary,
|
|
120
121
|
fontSize: 9,
|
|
122
|
+
lineHeight: 12,
|
|
121
123
|
}}
|
|
122
124
|
>
|
|
123
125
|
{tab.count}
|
|
@@ -13,6 +13,8 @@ export interface CalcuttaAuctionState {
|
|
|
13
13
|
bids: CalcuttaBidProps[];
|
|
14
14
|
my_bids: CalcuttaBidProps[];
|
|
15
15
|
paused: boolean;
|
|
16
|
+
escrow_summaries: Record<string, { escrow_balance: number; committed_balance: number }>;
|
|
17
|
+
total_escrow_balance: number;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export const useCalcuttaAuction = (calcutta_competition_id?: string, poll_interval: number = 5000, player_id?: string) => {
|
|
@@ -22,6 +24,8 @@ export const useCalcuttaAuction = (calcutta_competition_id?: string, poll_interv
|
|
|
22
24
|
bids: [],
|
|
23
25
|
my_bids: [],
|
|
24
26
|
paused: false,
|
|
27
|
+
escrow_summaries: {},
|
|
28
|
+
total_escrow_balance: 0,
|
|
25
29
|
});
|
|
26
30
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
27
31
|
|
|
@@ -36,6 +40,8 @@ export const useCalcuttaAuction = (calcutta_competition_id?: string, poll_interv
|
|
|
36
40
|
bids: resp.bids,
|
|
37
41
|
my_bids: resp.my_bids,
|
|
38
42
|
paused: resp.competition?.auction_status === 'paused',
|
|
43
|
+
escrow_summaries: resp.escrow_summaries || {},
|
|
44
|
+
total_escrow_balance: resp.total_escrow_balance || 0,
|
|
39
45
|
});
|
|
40
46
|
} catch {
|
|
41
47
|
// Silently fail on poll
|
|
@@ -62,7 +68,7 @@ export const useCalcuttaAuction = (calcutta_competition_id?: string, poll_interv
|
|
|
62
68
|
}, []);
|
|
63
69
|
|
|
64
70
|
// Socket event handlers for live mode — update local state directly
|
|
65
|
-
const handleBidUpdate = useCallback((data: { item: CalcuttaAuctionItemProps; bid: CalcuttaBidProps; item_deadline?: string }) => {
|
|
71
|
+
const handleBidUpdate = useCallback((data: { item: CalcuttaAuctionItemProps; bid: CalcuttaBidProps; item_deadline?: string; outbid_player_id?: string }) => {
|
|
66
72
|
setState(prev => {
|
|
67
73
|
const newState = {
|
|
68
74
|
...prev,
|
|
@@ -70,7 +76,7 @@ export const useCalcuttaAuction = (calcutta_competition_id?: string, poll_interv
|
|
|
70
76
|
i.calcutta_auction_item_id === data.item.calcutta_auction_item_id ? data.item : i
|
|
71
77
|
),
|
|
72
78
|
bids: [...prev.bids.filter(b => b.calcutta_bid_id !== data.bid.calcutta_bid_id), data.bid],
|
|
73
|
-
my_bids: prev.my_bids,
|
|
79
|
+
my_bids: [...prev.my_bids],
|
|
74
80
|
};
|
|
75
81
|
// Update my_bids if this bid belongs to the current user
|
|
76
82
|
if (player_id && data.bid.player_id === player_id) {
|
|
@@ -79,6 +85,14 @@ export const useCalcuttaAuction = (calcutta_competition_id?: string, poll_interv
|
|
|
79
85
|
data.bid,
|
|
80
86
|
];
|
|
81
87
|
}
|
|
88
|
+
// If I was outbid, mark my bid on this item as 'outbid'
|
|
89
|
+
if (player_id && data.outbid_player_id === player_id) {
|
|
90
|
+
newState.my_bids = newState.my_bids.map(b =>
|
|
91
|
+
b.calcutta_auction_item_id === data.item.calcutta_auction_item_id && b.bid_status === 'active'
|
|
92
|
+
? { ...b, bid_status: 'outbid' }
|
|
93
|
+
: b
|
|
94
|
+
);
|
|
95
|
+
}
|
|
82
96
|
return newState;
|
|
83
97
|
});
|
|
84
98
|
}, [player_id]);
|
|
@@ -48,5 +48,9 @@ export const useCalcuttaEscrow = (calcutta_competition_id?: string) => {
|
|
|
48
48
|
}
|
|
49
49
|
}, [calcutta_competition_id]);
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
const updateEscrowLocally = useCallback((escrow: CalcuttaEscrowProps) => {
|
|
52
|
+
setState({ loading: false, escrow });
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
return { ...state, fetchEscrow, deposit, withdraw, updateEscrowLocally };
|
|
52
56
|
};
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CalcuttaAuctionItemProps,
|
|
5
5
|
CalcuttaBidProps,
|
|
6
6
|
} from '@bettoredge/types';
|
|
7
|
+
import { useSocket } from '@bettoredge/socket';
|
|
7
8
|
|
|
8
9
|
export interface CalcuttaPresencePlayer {
|
|
9
10
|
player_id: string;
|
|
@@ -24,108 +25,105 @@ export interface CalcuttaSocketCallbacks {
|
|
|
24
25
|
onAuctionPaused?: () => void;
|
|
25
26
|
onAuctionResumed?: (data: { item?: CalcuttaAuctionItemProps; item_deadline?: string }) => void;
|
|
26
27
|
onAuctionClosed?: (data: { competition: CalcuttaCompetitionProps }) => void;
|
|
28
|
+
/** Triggered when another client requests all clients to refresh their data */
|
|
29
|
+
onRefresh?: () => void;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export const useCalcuttaSocket = (
|
|
30
33
|
calcutta_competition_id: string,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
getDeviceId: () => string | undefined,
|
|
34
|
+
access_token?: string,
|
|
35
|
+
device_id?: string,
|
|
36
|
+
player_data?: { username?: string; profile_pic?: string },
|
|
35
37
|
callbacks?: CalcuttaSocketCallbacks,
|
|
36
38
|
) => {
|
|
37
|
-
const
|
|
39
|
+
const { on, send, joinRoom, leaveRoom, ready, state } = useSocket({
|
|
40
|
+
access_token,
|
|
41
|
+
device_id,
|
|
42
|
+
autoConnect: !!access_token && !!device_id,
|
|
43
|
+
});
|
|
44
|
+
|
|
38
45
|
const [presence, setPresence] = useState<CalcuttaPresence>({ count: 0, players: [] });
|
|
39
|
-
const joinedRef = useRef(false);
|
|
40
46
|
const callbacksRef = useRef(callbacks);
|
|
41
47
|
callbacksRef.current = callbacks;
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
// Join room + subscribe to events
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!ready || !calcutta_competition_id) return;
|
|
52
|
+
|
|
53
|
+
joinRoom('JOIN_CALCUTTA_AUCTION', {
|
|
54
|
+
access_token,
|
|
55
|
+
device_id,
|
|
56
|
+
calcutta_competition_id,
|
|
57
|
+
player_data: player_data || {},
|
|
58
|
+
});
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
const unsubs = [
|
|
61
|
+
on('CALCUTTA_PRESENCE_UPDATE', (data: any) => {
|
|
62
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
63
|
+
setPresence({ count: data.count, players: data.players || [] });
|
|
64
|
+
}
|
|
65
|
+
}),
|
|
66
|
+
on('V1_SOCKET_JOIN_CALCUTTA', (data: any) => {
|
|
67
|
+
if (data.status === 'success' && data.players) {
|
|
68
|
+
setPresence({ count: data.count, players: data.players });
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
71
|
+
on('CALCUTTA_BID_UPDATE', (data: any) => {
|
|
72
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
50
73
|
callbacksRef.current?.onBidUpdate?.(data);
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
on('CALCUTTA_ITEM_ACTIVE', (data: any) => {
|
|
77
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
53
78
|
callbacksRef.current?.onItemActive?.(data);
|
|
54
|
-
|
|
55
|
-
|
|
79
|
+
}
|
|
80
|
+
}),
|
|
81
|
+
on('CALCUTTA_ITEM_SOLD', (data: any) => {
|
|
82
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
56
83
|
callbacksRef.current?.onItemSold?.(data);
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
}
|
|
85
|
+
}),
|
|
86
|
+
on('CALCUTTA_ITEM_UNSOLD', (data: any) => {
|
|
87
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
59
88
|
callbacksRef.current?.onItemUnsold?.(data);
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
}
|
|
90
|
+
}),
|
|
91
|
+
on('CALCUTTA_AUCTION_PAUSED', (data: any) => {
|
|
92
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
62
93
|
callbacksRef.current?.onAuctionPaused?.();
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
}
|
|
95
|
+
}),
|
|
96
|
+
on('CALCUTTA_AUCTION_RESUMED', (data: any) => {
|
|
97
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
65
98
|
callbacksRef.current?.onAuctionResumed?.(data);
|
|
66
|
-
|
|
67
|
-
|
|
99
|
+
}
|
|
100
|
+
}),
|
|
101
|
+
on('CALCUTTA_AUCTION_CLOSED', (data: any) => {
|
|
102
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
68
103
|
callbacksRef.current?.onAuctionClosed?.(data);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
setPresence({ count: data.count, players: data.players || [] });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
// Ignore non-JSON or malformed messages
|
|
84
|
-
}
|
|
85
|
-
}, [calcutta_competition_id]);
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
const ws = getSocket();
|
|
89
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
90
|
-
|
|
91
|
-
const access_token = getAccessToken();
|
|
92
|
-
const device_id = getDeviceId();
|
|
93
|
-
if (!access_token || !device_id) return;
|
|
94
|
-
|
|
95
|
-
// Add listener
|
|
96
|
-
// @ts-expect-error React Native WebSocket type lacks 'message' overload but it works at runtime
|
|
97
|
-
ws.addEventListener('message', handleMessage);
|
|
98
|
-
|
|
99
|
-
// Send join message
|
|
100
|
-
if (!joinedRef.current) {
|
|
101
|
-
ws.send(JSON.stringify({
|
|
102
|
-
type: 'JOIN_CALCUTTA_AUCTION',
|
|
103
|
-
access_token,
|
|
104
|
-
device_id,
|
|
105
|
-
calcutta_competition_id,
|
|
106
|
-
player_data,
|
|
107
|
-
}));
|
|
108
|
-
joinedRef.current = true;
|
|
109
|
-
}
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
on('CALCUTTA_REFRESH', (data: any) => {
|
|
107
|
+
if (data.calcutta_competition_id === calcutta_competition_id) {
|
|
108
|
+
callbacksRef.current?.onRefresh?.();
|
|
109
|
+
}
|
|
110
|
+
}),
|
|
111
|
+
];
|
|
110
112
|
|
|
111
113
|
return () => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Send leave message
|
|
116
|
-
if (joinedRef.current && ws.readyState === WebSocket.OPEN) {
|
|
117
|
-
ws.send(JSON.stringify({
|
|
118
|
-
type: 'LEAVE_CALCUTTA_AUCTION',
|
|
119
|
-
access_token,
|
|
120
|
-
device_id,
|
|
121
|
-
calcutta_competition_id,
|
|
122
|
-
}));
|
|
123
|
-
joinedRef.current = false;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
setConnected(false);
|
|
114
|
+
unsubs.forEach(u => u());
|
|
115
|
+
leaveRoom('LEAVE_CALCUTTA_AUCTION', { access_token, device_id, calcutta_competition_id });
|
|
127
116
|
};
|
|
128
|
-
}, [
|
|
117
|
+
}, [ready, calcutta_competition_id]);
|
|
118
|
+
|
|
119
|
+
/** Broadcast an event to all other clients in this calcutta room (bypasses RabbitMQ) */
|
|
120
|
+
const broadcast = useCallback((event: Record<string, any>) => {
|
|
121
|
+
send({
|
|
122
|
+
type: 'CALCUTTA_BROADCAST',
|
|
123
|
+
calcutta_competition_id,
|
|
124
|
+
event: { ...event, calcutta_competition_id },
|
|
125
|
+
});
|
|
126
|
+
}, [send, calcutta_competition_id]);
|
|
129
127
|
|
|
130
|
-
return { connected, presence };
|
|
128
|
+
return { connected: ready, presence, socketState: state, broadcast };
|
|
131
129
|
};
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,24 @@ export type { CalcuttaItemResultsProps } from './components/CalcuttaItemResults'
|
|
|
29
29
|
export { SweepstakesReveal } from './components/SweepstakesReveal';
|
|
30
30
|
export type { SweepstakesRevealProps } from './components/SweepstakesReveal';
|
|
31
31
|
|
|
32
|
+
// UX Components
|
|
33
|
+
export { CalcuttaActionCard } from './components/CalcuttaActionCard';
|
|
34
|
+
export type { CalcuttaActionCardProps } from './components/CalcuttaActionCard';
|
|
35
|
+
export { CalcuttaCountdown } from './components/CalcuttaCountdown';
|
|
36
|
+
export type { CalcuttaCountdownProps } from './components/CalcuttaCountdown';
|
|
37
|
+
export { EscrowWidget } from './components/EscrowWidget';
|
|
38
|
+
export type { EscrowWidgetProps } from './components/EscrowWidget';
|
|
39
|
+
export { AuctionInfoChips } from './components/AuctionInfoChips';
|
|
40
|
+
export type { AuctionInfoChipsProps } from './components/AuctionInfoChips';
|
|
41
|
+
export { AuctionCountdownOverlay } from './components/AuctionCountdownOverlay';
|
|
42
|
+
export type { AuctionCountdownOverlayProps } from './components/AuctionCountdownOverlay';
|
|
43
|
+
export { AuctionPausedOverlay } from './components/AuctionPausedOverlay';
|
|
44
|
+
export type { AuctionPausedOverlayProps } from './components/AuctionPausedOverlay';
|
|
45
|
+
export { ItemSoldCelebration } from './components/ItemSoldCelebration';
|
|
46
|
+
export type { ItemSoldCelebrationProps } from './components/ItemSoldCelebration';
|
|
47
|
+
export { AuctionCompleteOverlay } from './components/AuctionCompleteOverlay';
|
|
48
|
+
export type { AuctionCompleteOverlayProps } from './components/AuctionCompleteOverlay';
|
|
49
|
+
|
|
32
50
|
export { CalcuttaTemplateSelector } from './components/CalcuttaTemplateSelector';
|
|
33
51
|
export type { CalcuttaTemplateSelectorProps } from './components/CalcuttaTemplateSelector';
|
|
34
52
|
|