@dubsdotapp/expo 0.2.39 → 0.2.41

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.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/index.ts CHANGED
@@ -99,7 +99,7 @@ export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsT
99
99
  export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, DubsTheme } from './ui';
100
100
 
101
101
  // Game widgets
102
- export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet, ClaimPrizeSheet } from './ui';
102
+ export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet, ClaimPrizeSheet, ClaimButton } from './ui';
103
103
  export type {
104
104
  GamePosterProps,
105
105
  LivePoolsCardProps,
@@ -109,6 +109,7 @@ export type {
109
109
  CreateCustomGameSheetProps,
110
110
  JoinGameSheetProps,
111
111
  ClaimPrizeSheetProps,
112
+ ClaimButtonProps,
112
113
  } from './ui';
113
114
 
114
115
  // Utils
package/src/provider.tsx CHANGED
@@ -1,4 +1,6 @@
1
1
  import React, { createContext, useContext, useMemo, useCallback, useState, useEffect } from 'react';
2
+ import { ThemeOverrideProvider } from './ui/theme';
3
+ import type { DubsTheme } from './ui/theme';
2
4
  import { Connection } from '@solana/web3.js';
3
5
  import { DubsClient } from './client';
4
6
  import { NETWORK_CONFIG } from './constants';
@@ -104,49 +106,24 @@ export function DubsProvider({
104
106
  // Wait for config before rendering so accent color is applied on first paint
105
107
  if (uiConfig === null) return null;
106
108
 
109
+ // Build theme overrides from server config so all SDK components respect the tint
110
+ const themeOverrides: Partial<DubsTheme> = {};
111
+ if (uiConfig.accentColor) {
112
+ themeOverrides.accent = uiConfig.accentColor;
113
+ }
114
+
107
115
  // ── Path A: External wallet provided (BYOA) ──
108
116
  if (externalWallet) {
109
117
  return (
110
- <ExternalWalletProvider
111
- client={client}
112
- connection={connection}
113
- wallet={externalWallet}
114
- appName={uiConfig.appName || appName}
115
- network={network}
116
- storage={storage}
117
- managed={managed}
118
- renderLoading={renderLoading}
119
- renderError={renderError}
120
- renderRegistration={renderRegistration}
121
- accentColor={uiConfig.accentColor}
122
- uiConfig={uiConfig}
123
- >
124
- {children}
125
- </ExternalWalletProvider>
126
- );
127
- }
128
-
129
- // ── Path B: Managed MWA wallet ──
130
- return (
131
- <ManagedWalletProvider
132
- appName={uiConfig.appName || appName}
133
- cluster={cluster}
134
- storage={storage}
135
- renderConnectScreen={renderConnectScreen}
136
- accentColor={uiConfig.accentColor}
137
- appIcon={uiConfig.appIcon}
138
- tagline={uiConfig.tagline}
139
- redirectUri={redirectUri}
140
- appUrl={appUrl || uiConfig.appUrl}
141
- >
142
- {(adapter) => (
143
- <ManagedInner
118
+ <ThemeOverrideProvider value={themeOverrides}>
119
+ <ExternalWalletProvider
144
120
  client={client}
145
121
  connection={connection}
146
- wallet={adapter}
122
+ wallet={externalWallet}
147
123
  appName={uiConfig.appName || appName}
148
124
  network={network}
149
125
  storage={storage}
126
+ managed={managed}
150
127
  renderLoading={renderLoading}
151
128
  renderError={renderError}
152
129
  renderRegistration={renderRegistration}
@@ -154,9 +131,44 @@ export function DubsProvider({
154
131
  uiConfig={uiConfig}
155
132
  >
156
133
  {children}
157
- </ManagedInner>
158
- )}
159
- </ManagedWalletProvider>
134
+ </ExternalWalletProvider>
135
+ </ThemeOverrideProvider>
136
+ );
137
+ }
138
+
139
+ // ── Path B: Managed MWA wallet ──
140
+ return (
141
+ <ThemeOverrideProvider value={themeOverrides}>
142
+ <ManagedWalletProvider
143
+ appName={uiConfig.appName || appName}
144
+ cluster={cluster}
145
+ storage={storage}
146
+ renderConnectScreen={renderConnectScreen}
147
+ accentColor={uiConfig.accentColor}
148
+ appIcon={uiConfig.appIcon}
149
+ tagline={uiConfig.tagline}
150
+ redirectUri={redirectUri}
151
+ appUrl={appUrl || uiConfig.appUrl}
152
+ >
153
+ {(adapter) => (
154
+ <ManagedInner
155
+ client={client}
156
+ connection={connection}
157
+ wallet={adapter}
158
+ appName={uiConfig.appName || appName}
159
+ network={network}
160
+ storage={storage}
161
+ renderLoading={renderLoading}
162
+ renderError={renderError}
163
+ renderRegistration={renderRegistration}
164
+ accentColor={uiConfig.accentColor}
165
+ uiConfig={uiConfig}
166
+ >
167
+ {children}
168
+ </ManagedInner>
169
+ )}
170
+ </ManagedWalletProvider>
171
+ </ThemeOverrideProvider>
160
172
  );
161
173
  }
162
174
 
package/src/types.ts CHANGED
@@ -213,6 +213,8 @@ export interface GameDetail {
213
213
  status: string;
214
214
  league: string | null;
215
215
  lockTimestamp: number | null;
216
+ /** Winning side: 'home' | 'away' | 'draw' | null (null = refund) */
217
+ winnerSide: string | null;
216
218
  opponents: GameListOpponent[];
217
219
  bettors: Bettor[];
218
220
  homePool: number;
@@ -0,0 +1,122 @@
1
+ import React, { useState, useMemo, useCallback } from 'react';
2
+ import { StyleSheet, Text, TouchableOpacity, type ViewStyle } from 'react-native';
3
+ import { useDubsTheme } from '../theme';
4
+ import { useDubs } from '../../provider';
5
+ import { useGame } from '../../hooks/useGame';
6
+ import { useHasClaimed } from '../../hooks/useHasClaimed';
7
+ import { ClaimPrizeSheet } from './ClaimPrizeSheet';
8
+ import type { ClaimMutationResult } from '../../hooks/useClaim';
9
+
10
+ export interface ClaimButtonProps {
11
+ gameId: string;
12
+ style?: ViewStyle;
13
+ onSuccess?: (result: ClaimMutationResult) => void;
14
+ onError?: (error: Error) => void;
15
+ }
16
+
17
+ /**
18
+ * Drop-in claim button that handles eligibility checks, prize/refund display,
19
+ * and the claim sheet lifecycle internally.
20
+ *
21
+ * Renders nothing when the user is ineligible (lost, not resolved, not a bettor, etc.).
22
+ */
23
+ export function ClaimButton({ gameId, style, onSuccess, onError }: ClaimButtonProps) {
24
+ const t = useDubsTheme();
25
+ const { wallet } = useDubs();
26
+ const game = useGame(gameId);
27
+ const claimStatus = useHasClaimed(gameId);
28
+ const [sheetVisible, setSheetVisible] = useState(false);
29
+
30
+ const walletAddress = wallet.publicKey?.toBase58() ?? null;
31
+
32
+ const myBet = useMemo(() => {
33
+ if (!walletAddress || !game.data?.bettors) return null;
34
+ return game.data.bettors.find(b => b.wallet === walletAddress) ?? null;
35
+ }, [walletAddress, game.data?.bettors]);
36
+
37
+ const isResolved = game.data?.isResolved ?? false;
38
+ const isRefund = isResolved && game.data?.winnerSide === null;
39
+ const isWinner = isResolved && myBet != null && myBet.team === game.data?.winnerSide;
40
+ const isEligible = myBet != null && isResolved && (isWinner || isRefund);
41
+ const prizeAmount = isRefund ? (myBet?.amount ?? 0) : (game.data?.totalPool ?? 0);
42
+
43
+ const handleSuccess = useCallback(
44
+ (result: ClaimMutationResult) => {
45
+ claimStatus.refetch();
46
+ onSuccess?.(result);
47
+ },
48
+ [claimStatus.refetch, onSuccess], // eslint-disable-line react-hooks/exhaustive-deps
49
+ );
50
+
51
+ // Don't render while loading, missing data, or ineligible
52
+ if (game.loading || claimStatus.loading || !game.data || !walletAddress || !isEligible) {
53
+ return null;
54
+ }
55
+
56
+ const label = isRefund ? 'Refund' : 'Prize';
57
+
58
+ if (claimStatus.hasClaimed) {
59
+ return (
60
+ <TouchableOpacity
61
+ style={[styles.badge, { borderColor: t.accent }, style]}
62
+ activeOpacity={1}
63
+ disabled
64
+ >
65
+ <Text style={[styles.badgeText, { color: t.accent }]}>
66
+ {label} Claimed!
67
+ </Text>
68
+ </TouchableOpacity>
69
+ );
70
+ }
71
+
72
+ return (
73
+ <>
74
+ <TouchableOpacity
75
+ style={[styles.button, { backgroundColor: t.accent }, style]}
76
+ activeOpacity={0.8}
77
+ onPress={() => setSheetVisible(true)}
78
+ >
79
+ <Text style={styles.buttonText}>
80
+ Claim {label} — {prizeAmount} SOL
81
+ </Text>
82
+ </TouchableOpacity>
83
+
84
+ <ClaimPrizeSheet
85
+ visible={sheetVisible}
86
+ onDismiss={() => setSheetVisible(false)}
87
+ gameId={gameId}
88
+ prizeAmount={prizeAmount}
89
+ isRefund={isRefund}
90
+ onSuccess={handleSuccess}
91
+ onError={onError}
92
+ />
93
+ </>
94
+ );
95
+ }
96
+
97
+ const styles = StyleSheet.create({
98
+ button: {
99
+ height: 52,
100
+ borderRadius: 14,
101
+ justifyContent: 'center',
102
+ alignItems: 'center',
103
+ paddingHorizontal: 20,
104
+ },
105
+ buttonText: {
106
+ color: '#FFFFFF',
107
+ fontSize: 16,
108
+ fontWeight: '700',
109
+ },
110
+ badge: {
111
+ height: 52,
112
+ borderRadius: 14,
113
+ borderWidth: 2,
114
+ justifyContent: 'center',
115
+ alignItems: 'center',
116
+ paddingHorizontal: 20,
117
+ },
118
+ badgeText: {
119
+ fontSize: 16,
120
+ fontWeight: '700',
121
+ },
122
+ });
@@ -215,7 +215,7 @@ export function ClaimPrizeSheet({
215
215
  <TouchableOpacity
216
216
  style={[
217
217
  styles.ctaButton,
218
- { backgroundColor: canClaim ? t.success : t.border },
218
+ { backgroundColor: canClaim ? t.accent : t.border },
219
219
  ]}
220
220
  disabled={!canClaim}
221
221
  onPress={handleClaim}
@@ -42,7 +42,7 @@ export function JoinGameButton({ game, walletAddress, selectedTeam, status, onJo
42
42
  <Text style={[styles.buyInValue, { color: t.text }]}>{game.buyIn} SOL</Text>
43
43
  </View>
44
44
  <TouchableOpacity
45
- style={[styles.button, { backgroundColor: selectedTeam ? '#22D3EE' : t.border }]}
45
+ style={[styles.button, { backgroundColor: selectedTeam ? t.accent : t.border }]}
46
46
  disabled={!selectedTeam || isJoining}
47
47
  onPress={onJoin}
48
48
  activeOpacity={0.8}
@@ -14,3 +14,5 @@ export { JoinGameSheet } from './JoinGameSheet';
14
14
  export type { JoinGameSheetProps } from './JoinGameSheet';
15
15
  export { ClaimPrizeSheet } from './ClaimPrizeSheet';
16
16
  export type { ClaimPrizeSheetProps } from './ClaimPrizeSheet';
17
+ export { ClaimButton } from './ClaimButton';
18
+ export type { ClaimButtonProps } from './ClaimButton';
package/src/ui/index.ts CHANGED
@@ -10,7 +10,7 @@ export { useDubsTheme, mergeTheme } from './theme';
10
10
  export type { DubsTheme } from './theme';
11
11
 
12
12
  // Game widgets
13
- export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet, ClaimPrizeSheet } from './game';
13
+ export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet, ClaimPrizeSheet, ClaimButton } from './game';
14
14
  export type {
15
15
  GamePosterProps,
16
16
  LivePoolsCardProps,
@@ -20,4 +20,5 @@ export type {
20
20
  CreateCustomGameSheetProps,
21
21
  JoinGameSheetProps,
22
22
  ClaimPrizeSheetProps,
23
+ ClaimButtonProps,
23
24
  } from './game';
package/src/ui/theme.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { createContext, useContext } from 'react';
1
2
  import { useColorScheme } from 'react-native';
2
3
 
3
4
  export interface DubsTheme {
@@ -51,9 +52,16 @@ const light: DubsTheme = {
51
52
  errorBorder: '#FECACA',
52
53
  };
53
54
 
55
+ /** Context for provider-level theme overrides (e.g. accent color from developer config). */
56
+ const ThemeOverrideContext = createContext<Partial<DubsTheme>>({});
57
+ export const ThemeOverrideProvider = ThemeOverrideContext.Provider;
58
+
54
59
  export function useDubsTheme(): DubsTheme {
55
60
  const scheme = useColorScheme();
56
- return scheme === 'light' ? light : dark;
61
+ const base = scheme === 'light' ? light : dark;
62
+ const overrides = useContext(ThemeOverrideContext);
63
+ if (Object.keys(overrides).length === 0) return base;
64
+ return { ...base, ...overrides };
57
65
  }
58
66
 
59
67
  /** Merge overrides into a base theme (e.g. custom accent color from developer config) */