@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/dist/index.d.mts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +334 -219
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +175 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/provider.tsx +50 -38
- package/src/types.ts +2 -0
- package/src/ui/game/ClaimButton.tsx +122 -0
- package/src/ui/game/ClaimPrizeSheet.tsx +1 -1
- package/src/ui/game/JoinGameButton.tsx +1 -1
- package/src/ui/game/index.ts +2 -0
- package/src/ui/index.ts +2 -1
- package/src/ui/theme.ts +9 -1
package/package.json
CHANGED
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
|
-
<
|
|
111
|
-
|
|
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={
|
|
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
|
-
</
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
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 ?
|
|
45
|
+
style={[styles.button, { backgroundColor: selectedTeam ? t.accent : t.border }]}
|
|
46
46
|
disabled={!selectedTeam || isJoining}
|
|
47
47
|
onPress={onJoin}
|
|
48
48
|
activeOpacity={0.8}
|
package/src/ui/game/index.ts
CHANGED
|
@@ -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
|
-
|
|
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) */
|