@dubsdotapp/expo 0.5.14 → 0.5.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dubsdotapp/expo",
3
- "version": "0.5.14",
3
+ "version": "0.5.16",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -16,6 +16,10 @@ export { useAuth } from './useAuth';
16
16
  export type { UseAuthResult } from './useAuth';
17
17
  export { useUFCFightCard } from './useUFCFightCard';
18
18
  export { useUFCFighterDetail } from './useUFCFighterDetail';
19
+ export { useHighlights } from './useHighlights';
20
+ export type { HighlightVideo } from './useHighlights';
21
+ export { useShorts } from './useShorts';
22
+ export type { ShortVideo } from './useShorts';
19
23
  export { usePushNotifications } from './usePushNotifications';
20
24
  export type { PushNotificationStatus } from './usePushNotifications';
21
25
  export { useArcadePools } from './useArcadePools';
@@ -0,0 +1,38 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { useDubs } from '../provider';
3
+ import type { QueryResult } from '../types';
4
+
5
+ export interface HighlightVideo {
6
+ videoId: string;
7
+ title: string;
8
+ channelTitle: string;
9
+ thumbnail: string;
10
+ publishedAt: string;
11
+ }
12
+
13
+ export function useHighlights(league?: string, limit: number = 8): QueryResult<HighlightVideo[]> {
14
+ const { client } = useDubs();
15
+ const [data, setData] = useState<HighlightVideo[] | null>(null);
16
+ const [loading, setLoading] = useState(true);
17
+ const [error, setError] = useState<Error | null>(null);
18
+
19
+ const fetchData = useCallback(async () => {
20
+ setLoading(true);
21
+ setError(null);
22
+ try {
23
+ const qs = new URLSearchParams();
24
+ if (league) qs.set('league', league);
25
+ qs.set('limit', String(limit));
26
+ const res = await (client as any).request('GET', `/highlights?${qs.toString()}`);
27
+ setData(res.videos || []);
28
+ } catch (err) {
29
+ setError(err instanceof Error ? err : new Error(String(err)));
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ }, [client, league, limit]);
34
+
35
+ useEffect(() => { fetchData(); }, [fetchData]);
36
+
37
+ return { data, loading, error, refetch: fetchData };
38
+ }
@@ -0,0 +1,36 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { useDubs } from '../provider';
3
+ import type { QueryResult } from '../types';
4
+
5
+ export interface ShortVideo {
6
+ videoId: string;
7
+ title: string;
8
+ channelTitle: string;
9
+ thumbnail: string;
10
+ publishedAt: string;
11
+ }
12
+
13
+ export function useShorts(league: string = 'NBA', limit: number = 20): QueryResult<ShortVideo[]> {
14
+ const { client } = useDubs();
15
+ const [data, setData] = useState<ShortVideo[] | null>(null);
16
+ const [loading, setLoading] = useState(true);
17
+ const [error, setError] = useState<Error | null>(null);
18
+
19
+ const fetchData = useCallback(async () => {
20
+ setLoading(true);
21
+ setError(null);
22
+ try {
23
+ const qs = new URLSearchParams({ league, limit: String(limit) });
24
+ const res = await (client as any).request('GET', `/shorts?${qs.toString()}`);
25
+ setData(res.videos || []);
26
+ } catch (err) {
27
+ setError(err instanceof Error ? err : new Error(String(err)));
28
+ } finally {
29
+ setLoading(false);
30
+ }
31
+ }, [client, league, limit]);
32
+
33
+ useEffect(() => { fetchData(); }, [fetchData]);
34
+
35
+ return { data, loading, error, refetch: fetchData };
36
+ }
package/src/index.ts CHANGED
@@ -127,8 +127,12 @@ export type {
127
127
  ArcadeCountdown,
128
128
  UseArcadeBridgeOptions,
129
129
  UseArcadeBridgeResult,
130
+ HighlightVideo,
131
+ ShortVideo,
130
132
  } from './hooks';
131
133
 
134
+ export { useHighlights, useShorts } from './hooks';
135
+
132
136
  // UI
133
137
  export { AuthGate, ConnectWalletScreen, ConnectWalletButton, UserProfileCard, SettingsSheet, UserProfileSheet, useDubsTheme, mergeTheme } from './ui';
134
138
  export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, ConnectWalletButtonProps, AuthGateConnectWalletProps, UserProfileCardProps, SettingsSheetProps, UserProfileSheetProps, DubsTheme } from './ui';
@@ -27,9 +27,10 @@ export interface JoinGameSheetProps {
27
27
  ImageComponent?: React.ComponentType<any>;
28
28
  /** Custom short-name function for team labels */
29
29
  shortName?: (name: string | null) => string;
30
- /** Override team colors (default blue/red) */
30
+ /** Override team colors (default blue/red/green) */
31
31
  homeColor?: string;
32
32
  awayColor?: string;
33
+ drawColor?: string;
33
34
  /** Callbacks */
34
35
  onSuccess?: (result: JoinGameMutationResult) => void;
35
36
  onError?: (error: Error) => void;
@@ -72,6 +73,7 @@ export function JoinGameSheet({
72
73
  shortName,
73
74
  homeColor = '#3B82F6',
74
75
  awayColor = '#EF4444',
76
+ drawColor = '#F59E0B',
75
77
  onSuccess,
76
78
  onError,
77
79
  onTeamSelect,
@@ -151,21 +153,35 @@ export function JoinGameSheet({
151
153
  const totalPool = game.totalPool || 0;
152
154
  const homePool = game.homePool || 0;
153
155
  const awayPool = game.awayPool || 0;
156
+ const drawPool = game.drawPool || 0;
154
157
  const buyIn = game.buyIn;
155
158
 
159
+ // Show draw option if any bettor is on draw, or if the league supports it
160
+ const drawBettors = bettors.filter(b => b.team === 'draw');
161
+ const hasDrawOption = drawPool > 0 || drawBettors.length > 0 ||
162
+ (game.league && ['English Premier League', 'EPL', 'MLS', 'La Liga', 'Serie A', 'Bundesliga', 'Ligue 1'].some(l => (game.league || '').includes(l)));
163
+
156
164
  const poolAfterJoin = totalPool + wager;
157
165
 
158
- const { homeOdds, awayOdds, homeBets, awayBets } = useMemo(() => {
166
+ const { homeOdds, awayOdds, drawOdds, homeBets, awayBets, drawBets } = useMemo(() => {
167
+ const homeBetsCount = bettors.filter(b => b.team === 'home').length;
168
+ const awayBetsCount = bettors.filter(b => b.team === 'away').length;
169
+ const drawBetsCount = bettors.filter(b => b.team === 'draw').length;
159
170
  const newPool = totalPool + wager;
171
+ const newHome = homePool + (selectedTeam === 'home' ? wager : 0);
172
+ const newAway = awayPool + (selectedTeam === 'away' ? wager : 0);
173
+ const newDraw = drawPool + (selectedTeam === 'draw' ? wager : 0);
160
174
  return {
161
- homeOdds: homePool > 0 ? (newPool / (homePool + (selectedTeam === 'home' ? wager : 0))).toFixed(2) : '—',
162
- awayOdds: awayPool > 0 ? (newPool / (awayPool + (selectedTeam === 'away' ? wager : 0))).toFixed(2) : '—',
163
- homeBets: bettors.filter(b => b.team === 'home').length,
164
- awayBets: bettors.filter(b => b.team === 'away').length,
175
+ homeOdds: newHome > 0 ? (newPool / newHome).toFixed(2) : '—',
176
+ awayOdds: newAway > 0 ? (newPool / newAway).toFixed(2) : '—',
177
+ drawOdds: newDraw > 0 ? (newPool / newDraw).toFixed(2) : '',
178
+ homeBets: homeBetsCount,
179
+ awayBets: awayBetsCount,
180
+ drawBets: drawBetsCount,
165
181
  };
166
- }, [totalPool, homePool, awayPool, bettors, wager, selectedTeam]);
182
+ }, [totalPool, homePool, awayPool, drawPool, bettors, wager, selectedTeam]);
167
183
 
168
- const selectedOdds = selectedTeam === 'home' ? homeOdds : selectedTeam === 'away' ? awayOdds : '—';
184
+ const selectedOdds = selectedTeam === 'home' ? homeOdds : selectedTeam === 'away' ? awayOdds : selectedTeam === 'draw' ? drawOdds : '—';
169
185
  const potentialWinnings = selectedOdds !== '—' ? formatSol(parseFloat(selectedOdds) * wager) : '—';
170
186
 
171
187
  const homeName = shortName ? shortName(opponents[0]?.name) : (opponents[0]?.name || 'Home');
@@ -298,15 +314,28 @@ export function JoinGameSheet({
298
314
  t={t}
299
315
  />
300
316
  </View>
317
+ {hasDrawOption && (
318
+ <View style={styles.drawRow}>
319
+ <TeamButton
320
+ name="Draw"
321
+ odds={drawOdds}
322
+ bets={drawBets}
323
+ color={drawColor}
324
+ selected={selectedTeam === 'draw'}
325
+ onPress={() => { setSelectedTeam('draw' as any); onTeamSelect?.('draw' as any); }}
326
+ t={t}
327
+ />
328
+ </View>
329
+ )}
301
330
  </View>
302
331
  )}
303
332
 
304
333
  {/* Already joined — show which side */}
305
334
  {alreadyJoined && myBet && (
306
- <View style={[styles.myBetCard, { backgroundColor: (myBet.team === 'home' ? homeColor : awayColor) + '15', borderColor: myBet.team === 'home' ? homeColor : awayColor }]}>
307
- <Text style={[styles.myBetLabel, { color: myBet.team === 'home' ? homeColor : awayColor }]}>YOUR BET</Text>
335
+ <View style={[styles.myBetCard, { backgroundColor: (myBet.team === 'home' ? homeColor : myBet.team === 'away' ? awayColor : drawColor) + '15', borderColor: myBet.team === 'home' ? homeColor : myBet.team === 'away' ? awayColor : drawColor }]}>
336
+ <Text style={[styles.myBetLabel, { color: myBet.team === 'home' ? homeColor : myBet.team === 'away' ? awayColor : drawColor }]}>YOUR BET</Text>
308
337
  <Text style={[styles.myBetTeam, { color: t.text }]}>
309
- {myBet.team === 'home' ? homeName : awayName}
338
+ {myBet.team === 'home' ? homeName : myBet.team === 'away' ? awayName : 'Draw'}
310
339
  </Text>
311
340
  <Text style={[styles.myBetAmount, { color: t.textMuted }]}>
312
341
  {formatSol(myBet.amount)} SOL
@@ -536,6 +565,9 @@ const styles = StyleSheet.create({
536
565
  flexDirection: 'row',
537
566
  gap: 12,
538
567
  },
568
+ drawRow: {
569
+ marginTop: 8,
570
+ },
539
571
  summaryCard: {
540
572
  marginTop: 20,
541
573
  borderRadius: 16,