@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.
@@ -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.auction_start_datetime
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
- return { ...state, fetchEscrow, deposit, withdraw };
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
- player_data: { username: string; profile_pic?: string },
32
- getSocket: () => WebSocket | null,
33
- getAccessToken: () => string | undefined,
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 [connected, setConnected] = useState(false);
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
- const handleMessage = useCallback((event: MessageEvent) => {
44
- try {
45
- const data = JSON.parse(event.data);
46
- if (data.calcutta_competition_id && data.calcutta_competition_id !== calcutta_competition_id) return;
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
- switch (data.type) {
49
- case 'CALCUTTA_BID_UPDATE':
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
- break;
52
- case 'CALCUTTA_ITEM_ACTIVE':
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
- break;
55
- case 'CALCUTTA_ITEM_SOLD':
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
- break;
58
- case 'CALCUTTA_ITEM_UNSOLD':
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
- break;
61
- case 'CALCUTTA_AUCTION_PAUSED':
89
+ }
90
+ }),
91
+ on('CALCUTTA_AUCTION_PAUSED', (data: any) => {
92
+ if (data.calcutta_competition_id === calcutta_competition_id) {
62
93
  callbacksRef.current?.onAuctionPaused?.();
63
- break;
64
- case 'CALCUTTA_AUCTION_RESUMED':
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
- break;
67
- case 'CALCUTTA_AUCTION_CLOSED':
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
- break;
70
- case 'CALCUTTA_PRESENCE_UPDATE':
71
- setPresence({ count: data.count, players: data.players });
72
- break;
73
- case 'V1_SOCKET_JOIN_CALCUTTA':
74
- if (data.status === 'success') {
75
- setConnected(true);
76
- if (data.count != null) {
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
- // @ts-expect-error React Native WebSocket type lacks 'message' overload but it works at runtime
113
- ws.removeEventListener('message', handleMessage);
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
- }, [calcutta_competition_id, handleMessage, getSocket, getAccessToken, getDeviceId]);
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