@dubsdotapp/expo 0.5.17 → 0.5.19

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/src/index.ts CHANGED
@@ -77,6 +77,13 @@ export type {
77
77
  BuildArcadeEntryResult,
78
78
  EnterArcadePoolResult,
79
79
  ArcadePoolResult,
80
+ JackpotRound,
81
+ JackpotLastWinner,
82
+ JackpotEntry,
83
+ JackpotRoundResult,
84
+ JackpotConfig,
85
+ BuildJackpotEnterResult,
86
+ ConfirmJackpotEnterResult,
80
87
  } from './types';
81
88
 
82
89
  // Provider
@@ -111,6 +118,9 @@ export {
111
118
  useEnterArcadePool,
112
119
  useArcadeCountdown,
113
120
  useArcadeBridge,
121
+ useJackpot,
122
+ useJackpotHistory,
123
+ useEnterJackpot,
114
124
  } from './hooks';
115
125
  export type {
116
126
  CreateGameMutationResult,
@@ -129,6 +139,9 @@ export type {
129
139
  UseArcadeBridgeResult,
130
140
  HighlightVideo,
131
141
  ShortVideo,
142
+ UseJackpotResult,
143
+ UseJackpotHistoryResult,
144
+ EnterJackpotMutationResult,
132
145
  } from './hooks';
133
146
 
134
147
  export { useHighlights, useShorts } from './hooks';
@@ -155,6 +168,48 @@ export type {
155
168
  ArcadeLeaderboardSheetProps,
156
169
  } from './ui';
157
170
 
171
+ // Jackpot widgets
172
+ export { JackpotCard, JackpotSheet, JackpotWidget } from './ui';
173
+ export type { JackpotCardProps, JackpotSheetProps, JackpotWidgetProps } from './ui';
174
+
175
+ // Chat module
176
+ export {
177
+ ChatProvider,
178
+ useChatContext,
179
+ ChatSocket,
180
+ useChatStatus,
181
+ useChatMessages,
182
+ useSendMessage,
183
+ useOnlineUsers,
184
+ useUnreadCount,
185
+ useConversations,
186
+ useDirectMessages,
187
+ useFriends,
188
+ useFriendRequests,
189
+ useSearchUsers,
190
+ useSendFriendRequest,
191
+ useRespondToFriendRequest,
192
+ } from './chat';
193
+ export type {
194
+ ChatProviderProps,
195
+ ChatContextValue,
196
+ ChatSocketConfig,
197
+ ChatSocketListeners,
198
+ ChatMessage,
199
+ ChatMention,
200
+ ChatPayment,
201
+ DirectMessage,
202
+ Conversation,
203
+ FriendUser,
204
+ FriendRequest,
205
+ OnlineUser,
206
+ TypingEvent,
207
+ ChatNotification,
208
+ ChatConnectionStatus,
209
+ SendMessageParams,
210
+ SendDMParams,
211
+ } from './chat';
212
+
158
213
  // Utils
159
214
  export { signAndSendBase64Transaction } from './utils/transaction';
160
215
  export { getDeviceInfo, isSolanaSeeker } from './utils/device';
package/src/types.ts CHANGED
@@ -581,6 +581,66 @@ export interface ArcadePoolResult {
581
581
  best_score: number;
582
582
  }
583
583
 
584
+ // ── Jackpot ──
585
+
586
+ export interface JackpotRound {
587
+ roundId: string;
588
+ status: 'Open' | 'Locked' | 'Resolved';
589
+ totalPotLamports: string;
590
+ totalPotSol: number;
591
+ entryCount: number;
592
+ totalWeight: string;
593
+ timeRemainingSlots: number;
594
+ }
595
+
596
+ export interface JackpotLastWinner {
597
+ roundId: string;
598
+ winner: string;
599
+ winAmount: string;
600
+ winAmountSol: number;
601
+ totalPot: string;
602
+ entryCount: number;
603
+ timestamp: string | null;
604
+ }
605
+
606
+ export interface JackpotEntry {
607
+ player: string;
608
+ weight: string;
609
+ weightSol: number;
610
+ oddsPercent: string;
611
+ }
612
+
613
+ export interface JackpotRoundResult {
614
+ roundId: string;
615
+ winner: string;
616
+ winAmount: string;
617
+ winAmountSol: number;
618
+ totalPot: string;
619
+ totalPotSol: number;
620
+ entryCount: number;
621
+ timestamp: string;
622
+ }
623
+
624
+ export interface JackpotConfig {
625
+ feeBasisPoints: number;
626
+ roundDurationSlots: string;
627
+ minEntryLamports: number;
628
+ minEntrySol: number;
629
+ }
630
+
631
+ export interface BuildJackpotEnterResult {
632
+ transaction: string;
633
+ roundId: string;
634
+ amount: string;
635
+ amountSol: number;
636
+ }
637
+
638
+ export interface ConfirmJackpotEnterResult {
639
+ attributed: boolean;
640
+ appId: number;
641
+ signature: string;
642
+ }
643
+
584
644
  // ── UI Config (developer branding) ──
585
645
 
586
646
  export interface UiConfig {
@@ -590,4 +650,13 @@ export interface UiConfig {
590
650
  appUrl?: string;
591
651
  tagline?: string;
592
652
  environment?: 'sandbox' | 'production';
653
+ /**
654
+ * Whether the developer has uploaded push credentials in the dev portal.
655
+ * Consumers should gate their push opt-in UI on the relevant platform flag —
656
+ * e.g. only show the "Enable Notifications" screen if pushConfigured.android is true.
657
+ */
658
+ pushConfigured?: {
659
+ android: boolean;
660
+ ios: boolean;
661
+ };
593
662
  }
@@ -88,7 +88,7 @@ export function JoinGameSheet({
88
88
 
89
89
  const isCustomGame = game.gameMode === CUSTOM_GAME_MODE;
90
90
 
91
- const [selectedTeam, setSelectedTeam] = useState<'home' | 'away' | null>(null);
91
+ const [selectedTeam, setSelectedTeam] = useState<'home' | 'away' | 'draw' | null>(null);
92
92
  const [wager, setWager] = useState(game.buyIn);
93
93
  const [showSuccess, setShowSuccess] = useState(false);
94
94
 
package/src/ui/index.ts CHANGED
@@ -15,6 +15,10 @@ export type { DubsTheme } from './theme';
15
15
 
16
16
  // Game widgets
17
17
  export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, CreateGameSheet, JoinGameSheet, ClaimPrizeSheet, ClaimButton, EnterArcadePoolSheet, ArcadeLeaderboardSheet, SolSlider } from './game';
18
+
19
+ // Jackpot widgets
20
+ export { JackpotCard, JackpotSheet, JackpotWidget } from './jackpot';
21
+ export type { JackpotCardProps, JackpotSheetProps, JackpotWidgetProps } from './jackpot';
18
22
  export type {
19
23
  GamePosterProps,
20
24
  LivePoolsCardProps,
@@ -0,0 +1,417 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ Animated,
7
+ StyleSheet,
8
+ Image,
9
+ } from 'react-native';
10
+ import type { JackpotRound, JackpotLastWinner, JackpotEntry } from '../../types';
11
+ import type { ViewStyle } from 'react-native';
12
+
13
+ export interface JackpotCardProps {
14
+ round: JackpotRound | null;
15
+ lastWinner?: JackpotLastWinner | null;
16
+ entries?: JackpotEntry[];
17
+ onPress?: () => void;
18
+ style?: ViewStyle;
19
+ }
20
+
21
+ function formatSOL(lamports: string | number): string {
22
+ const val = typeof lamports === 'string' ? parseInt(lamports, 10) : lamports;
23
+ if (isNaN(val) || val === 0) return '0';
24
+ const sol = val / 1_000_000_000;
25
+ // No trailing zeros
26
+ if (sol >= 100) return sol.toFixed(0);
27
+ if (sol >= 1) return sol.toFixed(2);
28
+ if (sol >= 0.01) return sol.toFixed(3);
29
+ return sol.toFixed(4);
30
+ }
31
+
32
+ function truncateWallet(addr: string): string {
33
+ if (!addr || addr.length < 8) return addr || '';
34
+ return `${addr.slice(0, 4)}...${addr.slice(-4)}`;
35
+ }
36
+
37
+ export function JackpotCard({ round, lastWinner, entries, onPress, style }: JackpotCardProps) {
38
+ const shimmerAnim = useRef(new Animated.Value(-1)).current;
39
+ const pulseAnim = useRef(new Animated.Value(0.4)).current;
40
+
41
+ useEffect(() => {
42
+ // Shimmer sweep: matches web heroSweep (4s)
43
+ const shimmer = Animated.loop(
44
+ Animated.sequence([
45
+ Animated.timing(shimmerAnim, { toValue: 1, duration: 4000, useNativeDriver: true }),
46
+ Animated.delay(500),
47
+ Animated.timing(shimmerAnim, { toValue: -1, duration: 0, useNativeDriver: true }),
48
+ ]),
49
+ );
50
+ shimmer.start();
51
+
52
+ // Status dot pulse
53
+ const pulse = Animated.loop(
54
+ Animated.sequence([
55
+ Animated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true }),
56
+ Animated.timing(pulseAnim, { toValue: 0.4, duration: 1000, useNativeDriver: true }),
57
+ ]),
58
+ );
59
+ pulse.start();
60
+
61
+ return () => { shimmer.stop(); pulse.stop(); };
62
+ }, [shimmerAnim, pulseAnim]);
63
+
64
+ const potSol = round ? formatSOL(round.totalPotLamports) : '0';
65
+ const isOpen = round?.status === 'Open';
66
+ const entryCount = round?.entryCount ?? 0;
67
+ const totalWeight = round ? Number(BigInt(round.totalWeight || '0')) : 0;
68
+
69
+ return (
70
+ <TouchableOpacity
71
+ activeOpacity={0.9}
72
+ onPress={onPress}
73
+ style={[styles.card, style]}
74
+ >
75
+ {/* Green accent gradient background */}
76
+ <View style={styles.gradientBg} />
77
+
78
+ {/* Shimmer sweep overlay */}
79
+ <Animated.View
80
+ style={[
81
+ styles.shimmer,
82
+ {
83
+ transform: [{
84
+ translateX: shimmerAnim.interpolate({
85
+ inputRange: [-1, 1],
86
+ outputRange: [-400, 400],
87
+ }),
88
+ }],
89
+ },
90
+ ]}
91
+ />
92
+
93
+ {/* Bottom accent bar */}
94
+ <View style={styles.accentBar} />
95
+
96
+ {/* Content */}
97
+ <View style={styles.content}>
98
+ {/* Status badge */}
99
+ <View style={styles.statusRow}>
100
+ <View style={[styles.statusBadge, isOpen ? styles.statusOpen : styles.statusClosed]}>
101
+ {isOpen && (
102
+ <Animated.View style={[styles.statusDot, { opacity: pulseAnim }]} />
103
+ )}
104
+ <Text style={[styles.statusText, { color: isOpen ? '#22c55e' : '#9ca3af' }]}>
105
+ {isOpen ? 'Open' : round?.status ?? 'Loading'}
106
+ </Text>
107
+ </View>
108
+ <Text style={styles.entryCountText}>{entryCount} player{entryCount !== 1 ? 's' : ''}</Text>
109
+ </View>
110
+
111
+ {/* Hero pot section */}
112
+ <View style={styles.heroSection}>
113
+ <View style={styles.potInfo}>
114
+ <Text style={styles.jackpotLabel}>JACKPOT</Text>
115
+ <Text style={styles.potValue}>{potSol} SOL</Text>
116
+ </View>
117
+ <Text style={styles.potEmoji}>🤑</Text>
118
+ </View>
119
+
120
+ {/* Info grid: Your Chance / Players / Last Winner */}
121
+ <View style={styles.infoGrid}>
122
+ <View style={styles.infoCard}>
123
+ <Text style={styles.infoLabel}>PLAYERS</Text>
124
+ <Text style={styles.infoValue}>{entryCount}</Text>
125
+ </View>
126
+ <View style={styles.infoCard}>
127
+ <Text style={styles.infoLabel}>TOTAL POT</Text>
128
+ <Text style={[styles.infoValue, { color: '#4ade80' }]}>{potSol}</Text>
129
+ </View>
130
+ <View style={styles.infoCard}>
131
+ <Text style={styles.infoLabel}>LAST WIN</Text>
132
+ <Text style={[styles.infoValue, { color: '#22c55e' }]}>
133
+ {lastWinner ? formatSOL(lastWinner.winAmount) : '—'}
134
+ </Text>
135
+ </View>
136
+ </View>
137
+
138
+ {/* Player carousel (horizontal scroll of entry chips) */}
139
+ {entries && entries.length > 0 && (
140
+ <View style={styles.playersSection}>
141
+ <View style={styles.playersSectionHeader}>
142
+ <Text style={styles.playersSectionTitle}>Players in Round</Text>
143
+ <View style={styles.activeCountBadge}>
144
+ <Text style={styles.activeCountText}>{entries.length}</Text>
145
+ </View>
146
+ </View>
147
+ <View style={styles.playersCarousel}>
148
+ {entries.slice(0, 8).map((entry, i) => {
149
+ const odds = entry.oddsPercent;
150
+ return (
151
+ <View key={`${entry.player}-${i}`} style={styles.playerCard}>
152
+ {/* Avatar placeholder */}
153
+ <View style={styles.playerAvatar}>
154
+ <Text style={styles.playerAvatarText}>
155
+ {entry.player.slice(0, 2).toUpperCase()}
156
+ </Text>
157
+ </View>
158
+ <Text style={styles.playerWallet} numberOfLines={1}>
159
+ {truncateWallet(entry.player)}
160
+ </Text>
161
+ <Text style={styles.playerWager}>{entry.weightSol.toFixed(2)} SOL</Text>
162
+ <Text style={styles.playerOdds}>{odds}%</Text>
163
+ </View>
164
+ );
165
+ })}
166
+ {entries.length > 8 && (
167
+ <View style={styles.playerCardMore}>
168
+ <Text style={styles.playerMoreText}>+{entries.length - 8}</Text>
169
+ </View>
170
+ )}
171
+ </View>
172
+ </View>
173
+ )}
174
+
175
+ {/* Place Bet CTA */}
176
+ <TouchableOpacity
177
+ style={styles.placeBetButton}
178
+ activeOpacity={0.85}
179
+ onPress={onPress}
180
+ >
181
+ <Text style={styles.placeBetText}>Place Bet</Text>
182
+ </TouchableOpacity>
183
+ </View>
184
+ </TouchableOpacity>
185
+ );
186
+ }
187
+
188
+ const styles = StyleSheet.create({
189
+ card: {
190
+ borderRadius: 16,
191
+ borderWidth: 1,
192
+ borderColor: 'rgba(34, 197, 94, 0.2)',
193
+ backgroundColor: '#0c0c14',
194
+ overflow: 'hidden',
195
+ position: 'relative',
196
+ },
197
+ gradientBg: {
198
+ ...StyleSheet.absoluteFillObject,
199
+ backgroundColor: 'rgba(34, 197, 94, 0.04)',
200
+ },
201
+ shimmer: {
202
+ position: 'absolute',
203
+ top: 0,
204
+ bottom: 0,
205
+ width: 120,
206
+ backgroundColor: 'rgba(34, 197, 94, 0.08)',
207
+ },
208
+ accentBar: {
209
+ position: 'absolute',
210
+ bottom: 0,
211
+ left: 0,
212
+ right: '40%' as any,
213
+ height: 2,
214
+ backgroundColor: '#22c55e',
215
+ },
216
+ content: {
217
+ padding: 16,
218
+ gap: 12,
219
+ },
220
+ // Status row
221
+ statusRow: {
222
+ flexDirection: 'row',
223
+ alignItems: 'center',
224
+ gap: 8,
225
+ },
226
+ statusBadge: {
227
+ flexDirection: 'row',
228
+ alignItems: 'center',
229
+ gap: 6,
230
+ paddingHorizontal: 10,
231
+ paddingVertical: 4,
232
+ borderRadius: 100,
233
+ },
234
+ statusOpen: {
235
+ backgroundColor: 'rgba(34, 197, 94, 0.15)',
236
+ },
237
+ statusClosed: {
238
+ backgroundColor: 'rgba(156, 163, 175, 0.15)',
239
+ },
240
+ statusDot: {
241
+ width: 6,
242
+ height: 6,
243
+ borderRadius: 3,
244
+ backgroundColor: '#22c55e',
245
+ },
246
+ statusText: {
247
+ fontSize: 12,
248
+ fontWeight: '700',
249
+ },
250
+ entryCountText: {
251
+ fontSize: 12,
252
+ color: '#6b6b6b',
253
+ fontWeight: '500',
254
+ },
255
+ // Hero pot
256
+ heroSection: {
257
+ flexDirection: 'row',
258
+ alignItems: 'center',
259
+ justifyContent: 'space-between',
260
+ },
261
+ potInfo: {
262
+ flex: 1,
263
+ },
264
+ jackpotLabel: {
265
+ fontSize: 10,
266
+ fontWeight: '600',
267
+ color: '#6b6b6b',
268
+ letterSpacing: 3,
269
+ textTransform: 'uppercase',
270
+ marginBottom: 4,
271
+ },
272
+ potValue: {
273
+ fontSize: 36,
274
+ fontWeight: '900',
275
+ color: '#4ade80',
276
+ letterSpacing: -1,
277
+ },
278
+ potEmoji: {
279
+ fontSize: 40,
280
+ opacity: 0.2,
281
+ },
282
+ // Info grid
283
+ infoGrid: {
284
+ flexDirection: 'row',
285
+ gap: 8,
286
+ },
287
+ infoCard: {
288
+ flex: 1,
289
+ borderRadius: 12,
290
+ borderWidth: 1,
291
+ borderColor: '#1e1e2a',
292
+ backgroundColor: '#0c0c14',
293
+ padding: 12,
294
+ },
295
+ infoLabel: {
296
+ fontSize: 9,
297
+ fontWeight: '600',
298
+ color: '#6b6b6b',
299
+ letterSpacing: 2,
300
+ textTransform: 'uppercase',
301
+ marginBottom: 4,
302
+ },
303
+ infoValue: {
304
+ fontSize: 18,
305
+ fontWeight: '700',
306
+ color: '#FFFFFF',
307
+ },
308
+ // Players section
309
+ playersSection: {
310
+ borderRadius: 12,
311
+ borderWidth: 1,
312
+ borderColor: '#1e1e2a',
313
+ backgroundColor: 'rgba(139, 92, 246, 0.04)',
314
+ padding: 12,
315
+ overflow: 'hidden',
316
+ },
317
+ playersSectionHeader: {
318
+ flexDirection: 'row',
319
+ alignItems: 'center',
320
+ justifyContent: 'space-between',
321
+ marginBottom: 10,
322
+ },
323
+ playersSectionTitle: {
324
+ fontSize: 13,
325
+ fontWeight: '600',
326
+ color: '#a0a0a0',
327
+ },
328
+ activeCountBadge: {
329
+ backgroundColor: 'rgba(34, 197, 94, 0.15)',
330
+ paddingHorizontal: 8,
331
+ paddingVertical: 2,
332
+ borderRadius: 100,
333
+ },
334
+ activeCountText: {
335
+ fontSize: 11,
336
+ fontWeight: '700',
337
+ color: '#22c55e',
338
+ },
339
+ playersCarousel: {
340
+ flexDirection: 'row',
341
+ gap: 10,
342
+ },
343
+ playerCard: {
344
+ width: 96,
345
+ borderRadius: 12,
346
+ borderWidth: 1.5,
347
+ borderColor: 'rgba(139, 92, 246, 0.4)',
348
+ backgroundColor: 'rgba(139, 92, 246, 0.08)',
349
+ padding: 10,
350
+ alignItems: 'center',
351
+ gap: 4,
352
+ },
353
+ playerAvatar: {
354
+ width: 40,
355
+ height: 40,
356
+ borderRadius: 20,
357
+ borderWidth: 1.5,
358
+ borderColor: '#8b5cf6',
359
+ backgroundColor: 'rgba(139, 92, 246, 0.2)',
360
+ alignItems: 'center',
361
+ justifyContent: 'center',
362
+ },
363
+ playerAvatarText: {
364
+ fontSize: 14,
365
+ fontWeight: '700',
366
+ color: '#a78bfa',
367
+ },
368
+ playerWallet: {
369
+ fontSize: 10,
370
+ fontWeight: '500',
371
+ color: '#a0a0a0',
372
+ width: '100%',
373
+ textAlign: 'center',
374
+ },
375
+ playerWager: {
376
+ fontSize: 12,
377
+ fontWeight: '600',
378
+ color: '#a78bfa',
379
+ },
380
+ playerOdds: {
381
+ fontSize: 10,
382
+ fontWeight: '700',
383
+ color: '#22c55e',
384
+ },
385
+ playerCardMore: {
386
+ width: 96,
387
+ borderRadius: 12,
388
+ borderWidth: 1.5,
389
+ borderColor: '#1e1e2a',
390
+ backgroundColor: '#14141e',
391
+ alignItems: 'center',
392
+ justifyContent: 'center',
393
+ },
394
+ playerMoreText: {
395
+ fontSize: 16,
396
+ fontWeight: '700',
397
+ color: '#6b6b6b',
398
+ },
399
+ // Place bet CTA
400
+ placeBetButton: {
401
+ height: 52,
402
+ borderRadius: 12,
403
+ alignItems: 'center',
404
+ justifyContent: 'center',
405
+ backgroundColor: '#22c55e',
406
+ shadowColor: '#22c55e',
407
+ shadowOffset: { width: 0, height: 4 },
408
+ shadowOpacity: 0.25,
409
+ shadowRadius: 12,
410
+ elevation: 6,
411
+ },
412
+ placeBetText: {
413
+ color: '#FFFFFF',
414
+ fontSize: 16,
415
+ fontWeight: '700',
416
+ },
417
+ });