@dubsdotapp/expo 0.5.4 → 0.5.6

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.4",
3
+ "version": "0.5.6",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -94,6 +94,10 @@ export function useArcadeBridge({
94
94
  const sessionTokenRef = useRef<string | null>(null);
95
95
  const gameStartTimeRef = useRef<number>(0);
96
96
 
97
+ // Keep canPlay in a ref so TAP_PLAY handler always sees the latest value
98
+ const canPlayRef = useRef(canPlay);
99
+ canPlayRef.current = canPlay;
100
+
97
101
  const [lastResult, setLastResult] = useState<SubmitScoreResult | null>(null);
98
102
  const [bridgeLoading, setBridgeLoading] = useState(false);
99
103
 
@@ -139,12 +143,13 @@ export function useArcadeBridge({
139
143
  return; // non-JSON postMessages from the game's own code — ignore
140
144
  }
141
145
 
142
- // Only process messages from our bridge protocol
143
- if (data.dubsArcade !== PROTOCOL_VERSION) return;
146
+ // If dubsArcade version is present it must match — if absent, accept anyway
147
+ // (backward-compat: old game builds don't include the envelope yet)
148
+ if (data.dubsArcade !== undefined && data.dubsArcade !== PROTOCOL_VERSION) return;
144
149
 
145
150
  switch (data.type) {
146
151
  case 'TAP_PLAY': {
147
- if (canPlay) {
152
+ if (canPlayRef.current) {
148
153
  triggerPlay();
149
154
  }
150
155
  return;
@@ -186,7 +191,7 @@ export function useArcadeBridge({
186
191
  return;
187
192
  }
188
193
  },
189
- [canPlay, triggerPlay, submitScore, onScoreSubmitted, onError],
194
+ [triggerPlay, submitScore, onScoreSubmitted, onError],
190
195
  );
191
196
 
192
197
  return { webviewRef, handleMessage, triggerPlay, lastResult, bridgeLoading };
package/src/index.ts CHANGED
@@ -131,7 +131,7 @@ export type {
131
131
 
132
132
  // UI
133
133
  export { AuthGate, ConnectWalletScreen, ConnectWalletButton, UserProfileCard, SettingsSheet, UserProfileSheet, useDubsTheme, mergeTheme } from './ui';
134
- export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, ConnectWalletButtonProps, UserProfileCardProps, SettingsSheetProps, UserProfileSheetProps, DubsTheme } from './ui';
134
+ export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, ConnectWalletButtonProps, AuthGateConnectWalletProps, UserProfileCardProps, SettingsSheetProps, UserProfileSheetProps, DubsTheme } from './ui';
135
135
 
136
136
  // Game widgets
137
137
  export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet, ClaimPrizeSheet, ClaimButton, EnterArcadePoolSheet, ArcadeLeaderboardSheet } from './ui';
@@ -31,6 +31,15 @@ export interface RegistrationScreenProps {
31
31
  client: DubsClient;
32
32
  }
33
33
 
34
+ export interface ConnectWalletScreenProps {
35
+ /** Call this to trigger the wallet connection flow */
36
+ onConnect: () => void;
37
+ /** True while the wallet is connecting/signing */
38
+ connecting: boolean;
39
+ /** Error from a failed connection attempt */
40
+ error: Error | null;
41
+ }
42
+
34
43
  export interface AuthGateProps {
35
44
  children: React.ReactNode;
36
45
  onSaveToken: (token: string | null) => void | Promise<void>;
@@ -38,6 +47,12 @@ export interface AuthGateProps {
38
47
  renderLoading?: (status: AuthStatus) => React.ReactNode;
39
48
  renderError?: (error: Error, retry: () => void) => React.ReactNode;
40
49
  renderRegistration?: (props: RegistrationScreenProps) => React.ReactNode;
50
+ /**
51
+ * Render your own connect-wallet screen.
52
+ * Receives { onConnect, connecting, error }.
53
+ * If omitted, the default ConnectWalletScreen is shown.
54
+ */
55
+ renderConnectWallet?: (props: ConnectWalletScreenProps) => React.ReactNode;
41
56
  appName?: string;
42
57
  /** Override accent color for registration screens (from developer UI config) */
43
58
  accentColor?: string;
@@ -52,6 +67,7 @@ export function AuthGate({
52
67
  renderLoading,
53
68
  renderError,
54
69
  renderRegistration,
70
+ renderConnectWallet,
55
71
  appName = 'Dubs',
56
72
  accentColor,
57
73
  }: AuthGateProps) {
@@ -162,6 +178,17 @@ export function AuthGate({
162
178
  return <DefaultErrorScreen error={auth.error} onRetry={retry} appName={appName} accentColor={accentColor} />;
163
179
  }
164
180
 
181
+ // idle = waiting for user to connect — show connect wallet screen
182
+ if (auth.status === 'idle') {
183
+ const connectProps: ConnectWalletScreenProps = {
184
+ onConnect: auth.authenticate,
185
+ connecting: false,
186
+ error: null,
187
+ };
188
+ if (renderConnectWallet) return <>{renderConnectWallet(connectProps)}</>;
189
+ // Fall through to default loading (existing behaviour if no renderConnectWallet)
190
+ }
191
+
165
192
  if (renderLoading) return <>{renderLoading(auth.status)}</>;
166
193
  return <DefaultLoadingScreen status={auth.status} appName={appName} accentColor={accentColor} />;
167
194
  }
@@ -1,112 +1,107 @@
1
1
  import React from 'react';
2
2
  import {
3
- Text,
4
3
  TouchableOpacity,
4
+ Text,
5
5
  ActivityIndicator,
6
6
  StyleSheet,
7
7
  View,
8
- type ViewStyle,
9
8
  } from 'react-native';
9
+ import { useAuth } from '../hooks/useAuth';
10
10
  import { useDubsTheme } from './theme';
11
11
 
12
12
  export interface ConnectWalletButtonProps {
13
- /** Called when the user taps the button */
14
- onConnect: () => void | Promise<void>;
15
- /** Show a loading spinner while connecting */
16
- connecting?: boolean;
17
- /** Error message to display below the button */
18
- error?: string | null;
19
- /** Override accent color (e.g. from developer UI config) */
20
- accentColor?: string;
21
- /** Override button label. Defaults to "Connect Wallet" */
13
+ /** Button label. Defaults to "Connect Wallet" */
22
14
  label?: string;
23
- /** Additional style applied to the outer container */
24
- style?: ViewStyle;
25
- /** Additional style applied to the button itself */
26
- buttonStyle?: ViewStyle;
15
+ /** Override accent/background color */
16
+ accentColor?: string;
17
+ /** Override text color. Defaults to #000 */
18
+ textColor?: string;
19
+ /** Override border radius */
20
+ borderRadius?: number;
21
+ /** Override padding vertical */
22
+ paddingVertical?: number;
23
+ /** Override font size */
24
+ fontSize?: number;
25
+ /** Full width. Defaults to true */
26
+ fullWidth?: boolean;
27
+ /** Called after authenticate() is triggered (not after it completes) */
28
+ onPress?: () => void;
27
29
  }
28
30
 
29
31
  /**
30
- * Drop-in wallet connect button that triggers the same MWA / Phantom
31
- * connection flow as ConnectWalletScreen, but renders as a single button
32
- * instead of a full-screen layout.
32
+ * ConnectWalletButton
33
33
  *
34
- * Use with `renderConnectScreen` on DubsProvider to embed the button
35
- * inside your own custom onboarding or home screen:
34
+ * A minimal, customisable button that fires useAuth().authenticate().
35
+ * Use this when you want to own the connect-wallet screen UI (e.g. inside
36
+ * your own onboarding) but still hand off the wallet picker, signing, and
37
+ * registration flow to the SDK.
36
38
  *
37
- * ```tsx
38
- * <DubsProvider
39
- * renderConnectScreen={({ onConnect, connecting, error }) => (
40
- * <MyScreen>
41
- * <ConnectWalletButton onConnect={onConnect} connecting={connecting} error={error} />
42
- * </MyScreen>
43
- * )}
44
- * >
45
- * ```
39
+ * The SDK will handle everything after this tap:
40
+ * wallet picker → sign message → (registration/avatar if new user) → authenticated
41
+ *
42
+ * Example:
43
+ * <ConnectWalletButton
44
+ * label="Let's go"
45
+ * accentColor="#22C55E"
46
+ * textColor="#000"
47
+ * />
46
48
  */
47
49
  export function ConnectWalletButton({
48
- onConnect,
49
- connecting = false,
50
- error = null,
51
- accentColor,
52
50
  label = 'Connect Wallet',
53
- style,
54
- buttonStyle,
51
+ accentColor,
52
+ textColor = '#000000',
53
+ borderRadius = 16,
54
+ paddingVertical = 16,
55
+ fontSize = 17,
56
+ fullWidth = true,
57
+ onPress,
55
58
  }: ConnectWalletButtonProps) {
56
59
  const t = useDubsTheme();
60
+ const { authenticate, status } = useAuth();
61
+
62
+ const connecting = status === 'authenticating' || status === 'signing' || status === 'verifying';
57
63
  const accent = accentColor || t.accent;
58
64
 
59
- return (
60
- <View style={style}>
61
- {error ? (
62
- <View
63
- style={[
64
- styles.errorBox,
65
- { backgroundColor: t.errorBg, borderColor: t.errorBorder },
66
- ]}
67
- >
68
- <Text style={[styles.errorText, { color: t.errorText }]}>{error}</Text>
69
- </View>
70
- ) : null}
65
+ const handlePress = () => {
66
+ onPress?.();
67
+ authenticate();
68
+ };
71
69
 
72
- <TouchableOpacity
73
- style={[styles.button, { backgroundColor: accent }, buttonStyle]}
74
- onPress={onConnect}
75
- disabled={connecting}
76
- activeOpacity={0.8}
77
- >
78
- {connecting ? (
79
- <ActivityIndicator color="#FFFFFF" size="small" />
80
- ) : (
81
- <Text style={styles.buttonText}>{label}</Text>
82
- )}
83
- </TouchableOpacity>
84
- </View>
70
+ return (
71
+ <TouchableOpacity
72
+ onPress={handlePress}
73
+ disabled={connecting}
74
+ activeOpacity={0.85}
75
+ style={[
76
+ styles.btn,
77
+ {
78
+ backgroundColor: accent,
79
+ borderRadius,
80
+ paddingVertical,
81
+ width: fullWidth ? '100%' : undefined,
82
+ opacity: connecting ? 0.8 : 1,
83
+ },
84
+ ]}
85
+ >
86
+ {connecting ? (
87
+ <ActivityIndicator color={textColor} size="small" />
88
+ ) : (
89
+ <Text style={[styles.label, { color: textColor, fontSize }]}>
90
+ {label}
91
+ </Text>
92
+ )}
93
+ </TouchableOpacity>
85
94
  );
86
95
  }
87
96
 
88
97
  const styles = StyleSheet.create({
89
- button: {
90
- height: 56,
91
- borderRadius: 16,
92
- justifyContent: 'center',
98
+ btn: {
93
99
  alignItems: 'center',
100
+ justifyContent: 'center',
94
101
  paddingHorizontal: 24,
95
102
  },
96
- buttonText: {
97
- color: '#FFFFFF',
98
- fontSize: 18,
99
- fontWeight: '700',
100
- },
101
- errorBox: {
102
- borderWidth: 1,
103
- borderRadius: 12,
104
- paddingHorizontal: 16,
105
- paddingVertical: 12,
106
- marginBottom: 12,
107
- },
108
- errorText: {
109
- fontSize: 14,
110
- textAlign: 'center',
103
+ label: {
104
+ fontWeight: '800',
105
+ letterSpacing: -0.2,
111
106
  },
112
107
  });
@@ -82,9 +82,10 @@ export function EnterArcadePoolSheet({
82
82
  }, [mutation.status, mutation.error]); // eslint-disable-line react-hooks/exhaustive-deps
83
83
 
84
84
  const buyInSol = (pool.buy_in_lamports / 1_000_000_000).toFixed(4);
85
- const totalPlayers = stats?.total_entries ?? pool.total_entries ?? 0;
85
+ const totalPlayers = stats?.total_entries ?? 0;
86
+ const totalBuyIns = pool.total_entries ?? totalPlayers;
86
87
  const topScore = stats?.top_score ?? 0;
87
- const potSol = ((pool.buy_in_lamports * Number(totalPlayers)) / 1_000_000_000).toFixed(4);
88
+ const potSol = ((pool.buy_in_lamports * Number(totalBuyIns)) / 1_000_000_000).toFixed(4);
88
89
 
89
90
  const isMutating = mutation.status !== 'idle' && mutation.status !== 'success' && mutation.status !== 'error';
90
91
  const canJoin = !isMutating && mutation.status !== 'success';
@@ -30,6 +30,8 @@ export interface JoinGameSheetProps {
30
30
  /** Callbacks */
31
31
  onSuccess?: (result: JoinGameMutationResult) => void;
32
32
  onError?: (error: Error) => void;
33
+ /** Called when the user taps a team card (useful for sound/haptics) */
34
+ onTeamSelect?: (team: 'home' | 'away') => void;
33
35
  /** Pool mode: hides team selection, auto-assigns team, shows "Join Pool" labels */
34
36
  isPoolModeEnabled?: boolean;
35
37
  }
@@ -53,6 +55,7 @@ export function JoinGameSheet({
53
55
  awayColor = '#EF4444',
54
56
  onSuccess,
55
57
  onError,
58
+ onTeamSelect,
56
59
  isPoolModeEnabled = false,
57
60
  }: JoinGameSheetProps) {
58
61
  const t = useDubsTheme();
@@ -194,7 +197,7 @@ export function JoinGameSheet({
194
197
  bets={homeBets}
195
198
  color={homeColor}
196
199
  selected={selectedTeam === 'home'}
197
- onPress={() => setSelectedTeam('home')}
200
+ onPress={() => { setSelectedTeam('home'); onTeamSelect?.('home'); }}
198
201
  ImageComponent={ImageComponent}
199
202
  t={t}
200
203
  />
@@ -205,7 +208,7 @@ export function JoinGameSheet({
205
208
  bets={awayBets}
206
209
  color={awayColor}
207
210
  selected={selectedTeam === 'away'}
208
- onPress={() => setSelectedTeam('away')}
211
+ onPress={() => { setSelectedTeam('away'); onTeamSelect?.('away'); }}
209
212
  ImageComponent={ImageComponent}
210
213
  t={t}
211
214
  />
package/src/ui/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { AuthGate } from './AuthGate';
2
- export type { AuthGateProps, RegistrationScreenProps } from './AuthGate';
2
+ export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps as AuthGateConnectWalletProps } from './AuthGate';
3
3
  export { ConnectWalletScreen } from './ConnectWalletScreen';
4
4
  export type { ConnectWalletScreenProps } from './ConnectWalletScreen';
5
5
  export { ConnectWalletButton } from './ConnectWalletButton';