@dubsdotapp/expo 0.2.29 → 0.2.30

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.29",
3
+ "version": "0.2.30",
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/client.ts CHANGED
@@ -215,6 +215,7 @@ export class DubsClient {
215
215
  gameAddress: res.gameAddress,
216
216
  transaction: res.transaction,
217
217
  lockTimestamp: res.lockTimestamp,
218
+ externalGameId: res.externalGameId ?? null,
218
219
  };
219
220
  }
220
221
 
@@ -267,6 +268,7 @@ export class DubsClient {
267
268
  const qs = new URLSearchParams();
268
269
  if (params?.wallet) qs.set('wallet', params.wallet);
269
270
  if (params?.status) qs.set('status', params.status);
271
+ if (params?.externalGameId) qs.set('externalGameId', params.externalGameId);
270
272
  if (params?.limit != null) qs.set('limit', String(params.limit));
271
273
  if (params?.offset != null) qs.set('offset', String(params.offset));
272
274
  const query = qs.toString();
@@ -9,6 +9,7 @@ export interface CreateCustomGameMutationResult {
9
9
  signature: string;
10
10
  explorerUrl: string;
11
11
  buyIn: number;
12
+ externalGameId?: string | null;
12
13
  }
13
14
 
14
15
  export function useCreateCustomGame() {
@@ -65,6 +66,7 @@ export function useCreateCustomGame() {
65
66
  signature,
66
67
  explorerUrl,
67
68
  buyIn: params.wagerAmount,
69
+ externalGameId: createResult.externalGameId ?? null,
68
70
  };
69
71
 
70
72
  setData(result);
package/src/index.ts CHANGED
@@ -95,7 +95,7 @@ export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsT
95
95
  export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, DubsTheme } from './ui';
96
96
 
97
97
  // Game widgets
98
- export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet } from './ui';
98
+ export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet } from './ui';
99
99
  export type {
100
100
  GamePosterProps,
101
101
  LivePoolsCardProps,
@@ -103,6 +103,7 @@ export type {
103
103
  PlayersCardProps,
104
104
  JoinGameButtonProps,
105
105
  CreateCustomGameSheetProps,
106
+ JoinGameSheetProps,
106
107
  } from './ui';
107
108
 
108
109
  // Utils
package/src/types.ts CHANGED
@@ -114,6 +114,7 @@ export interface CreateCustomGameParams {
114
114
  title?: string;
115
115
  maxPlayers?: number;
116
116
  metadata?: Record<string, unknown>;
117
+ externalGameId?: string;
117
118
  }
118
119
 
119
120
  export interface CreateCustomGameResult {
@@ -121,6 +122,7 @@ export interface CreateCustomGameResult {
121
122
  gameAddress: string;
122
123
  transaction: string;
123
124
  lockTimestamp: number;
125
+ externalGameId?: string | null;
124
126
  }
125
127
 
126
128
  // ── Join Game ──
@@ -203,6 +205,7 @@ export interface GameDetail {
203
205
  drawPool: number;
204
206
  totalPool: number;
205
207
  media: GameMedia;
208
+ externalGameId?: string | null;
206
209
  createdAt: string;
207
210
  updatedAt: string;
208
211
  }
@@ -226,6 +229,7 @@ export interface GameListItem {
226
229
  league: string | null;
227
230
  lockTimestamp: number | null;
228
231
  createdAt: string;
232
+ externalGameId?: string | null;
229
233
  opponents: GameListOpponent[];
230
234
  media: GameMedia;
231
235
  }
@@ -233,6 +237,7 @@ export interface GameListItem {
233
237
  export interface GetGamesParams {
234
238
  wallet?: string;
235
239
  status?: 'open' | 'locked' | 'resolved';
240
+ externalGameId?: string;
236
241
  limit?: number;
237
242
  offset?: number;
238
243
  }
@@ -25,6 +25,7 @@ export interface CreateCustomGameSheetProps {
25
25
  presetAmounts?: number[];
26
26
  defaultAmount?: number;
27
27
  metadata?: Record<string, unknown>;
28
+ externalGameId?: string;
28
29
  onAmountChange?: (amount: number | null) => void;
29
30
  onSuccess?: (result: CreateCustomGameMutationResult) => void;
30
31
  onError?: (error: Error) => void;
@@ -46,6 +47,7 @@ export function CreateCustomGameSheet({
46
47
  presetAmounts = [0.01, 0.1, 0.5, 1],
47
48
  defaultAmount = 0.01,
48
49
  metadata,
50
+ externalGameId,
49
51
  onAmountChange,
50
52
  onSuccess,
51
53
  onError,
@@ -139,11 +141,12 @@ export function CreateCustomGameSheet({
139
141
  title,
140
142
  maxPlayers,
141
143
  metadata,
144
+ externalGameId,
142
145
  });
143
146
  } catch {
144
147
  // Error is already captured in mutation state
145
148
  }
146
- }, [effectiveAmount, wallet.publicKey, mutation.execute, title, maxPlayers, metadata]); // eslint-disable-line react-hooks/exhaustive-deps
149
+ }, [effectiveAmount, wallet.publicKey, mutation.execute, title, maxPlayers, metadata, externalGameId]); // eslint-disable-line react-hooks/exhaustive-deps
147
150
 
148
151
  const statusLabel = STATUS_LABELS[mutation.status] || '';
149
152
 
@@ -0,0 +1,470 @@
1
+ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ ActivityIndicator,
7
+ Modal,
8
+ Animated,
9
+ StyleSheet,
10
+ KeyboardAvoidingView,
11
+ Platform,
12
+ } from 'react-native';
13
+ import { useDubsTheme } from '../theme';
14
+ import { useDubs } from '../../provider';
15
+ import { useJoinGame } from '../../hooks/useJoinGame';
16
+ import type { JoinGameMutationResult } from '../../hooks/useJoinGame';
17
+ import type { GameDetail } from '../../types';
18
+
19
+ export interface JoinGameSheetProps {
20
+ visible: boolean;
21
+ onDismiss: () => void;
22
+ game: GameDetail;
23
+ /** Optional Image component (expo-image, etc.) for team logos */
24
+ ImageComponent?: React.ComponentType<any>;
25
+ /** Custom short-name function for team labels */
26
+ shortName?: (name: string | null) => string;
27
+ /** Override team colors (default blue/red) */
28
+ homeColor?: string;
29
+ awayColor?: string;
30
+ /** Callbacks */
31
+ onSuccess?: (result: JoinGameMutationResult) => void;
32
+ onError?: (error: Error) => void;
33
+ }
34
+
35
+ const STATUS_LABELS: Record<string, string> = {
36
+ building: 'Building transaction...',
37
+ signing: 'Approve in wallet...',
38
+ confirming: 'Confirming...',
39
+ success: 'Joined!',
40
+ };
41
+
42
+ const CUSTOM_GAME_MODE = 6;
43
+
44
+ export function JoinGameSheet({
45
+ visible,
46
+ onDismiss,
47
+ game,
48
+ ImageComponent,
49
+ shortName,
50
+ homeColor = '#3B82F6',
51
+ awayColor = '#EF4444',
52
+ onSuccess,
53
+ onError,
54
+ }: JoinGameSheetProps) {
55
+ const t = useDubsTheme();
56
+ const { wallet } = useDubs();
57
+ const mutation = useJoinGame();
58
+
59
+ const isCustomGame = game.gameMode === CUSTOM_GAME_MODE;
60
+
61
+ // For custom games the joiner is always away — no team selection needed.
62
+ // For sports/esports games the user picks a team.
63
+ const [selectedTeam, setSelectedTeam] = useState<'home' | 'away' | null>(null);
64
+
65
+ const overlayOpacity = useRef(new Animated.Value(0)).current;
66
+
67
+ // Animate overlay on visibility change
68
+ useEffect(() => {
69
+ Animated.timing(overlayOpacity, {
70
+ toValue: visible ? 1 : 0,
71
+ duration: 250,
72
+ useNativeDriver: true,
73
+ }).start();
74
+ }, [visible, overlayOpacity]);
75
+
76
+ // Reset state when sheet opens
77
+ useEffect(() => {
78
+ if (visible) {
79
+ setSelectedTeam(isCustomGame ? 'away' : null);
80
+ mutation.reset();
81
+ }
82
+ }, [visible]); // eslint-disable-line react-hooks/exhaustive-deps
83
+
84
+ // Auto-dismiss on success
85
+ useEffect(() => {
86
+ if (mutation.status === 'success' && mutation.data) {
87
+ onSuccess?.(mutation.data);
88
+ const timer = setTimeout(() => {
89
+ onDismiss();
90
+ }, 1500);
91
+ return () => clearTimeout(timer);
92
+ }
93
+ }, [mutation.status, mutation.data]); // eslint-disable-line react-hooks/exhaustive-deps
94
+
95
+ // Report errors
96
+ useEffect(() => {
97
+ if (mutation.status === 'error' && mutation.error) {
98
+ onError?.(mutation.error);
99
+ }
100
+ }, [mutation.status, mutation.error]); // eslint-disable-line react-hooks/exhaustive-deps
101
+
102
+ // --- Derived values ---
103
+
104
+ const opponents = game.opponents || [];
105
+ const bettors = game.bettors || [];
106
+ const totalPool = game.totalPool || 0;
107
+ const homePool = game.homePool || 0;
108
+ const awayPool = game.awayPool || 0;
109
+ const buyIn = game.buyIn;
110
+
111
+ const { homeOdds, awayOdds, homeBets, awayBets } = useMemo(() => ({
112
+ homeOdds: homePool > 0 ? (totalPool / homePool).toFixed(2) : '—',
113
+ awayOdds: awayPool > 0 ? (totalPool / awayPool).toFixed(2) : '—',
114
+ homeBets: bettors.filter(b => b.team === 'home').length,
115
+ awayBets: bettors.filter(b => b.team === 'away').length,
116
+ }), [totalPool, homePool, awayPool, bettors]);
117
+
118
+ const poolAfterJoin = totalPool + buyIn;
119
+ const selectedOdds = selectedTeam === 'home' ? homeOdds : selectedTeam === 'away' ? awayOdds : '—';
120
+ const potentialWinnings = selectedOdds !== '—' ? (parseFloat(selectedOdds) * buyIn).toFixed(4) : '—';
121
+
122
+ const homeName = shortName ? shortName(opponents[0]?.name) : (opponents[0]?.name || 'Home');
123
+ const awayName = shortName ? shortName(opponents[1]?.name) : (opponents[1]?.name || 'Away');
124
+ const selectedName = selectedTeam === 'home' ? homeName : selectedTeam === 'away' ? awayName : '—';
125
+
126
+ const alreadyJoined = useMemo(() => {
127
+ if (!wallet.publicKey) return false;
128
+ const addr = wallet.publicKey.toBase58();
129
+ return bettors.some(b => b.wallet === addr);
130
+ }, [bettors, wallet.publicKey]);
131
+
132
+ const isMutating = mutation.status !== 'idle' && mutation.status !== 'success' && mutation.status !== 'error';
133
+ const canJoin = selectedTeam !== null && !isMutating && mutation.status !== 'success' && !alreadyJoined;
134
+
135
+ const handleJoin = useCallback(async () => {
136
+ if (!selectedTeam || !wallet.publicKey) return;
137
+
138
+ try {
139
+ await mutation.execute({
140
+ playerWallet: wallet.publicKey.toBase58(),
141
+ gameId: game.gameId,
142
+ teamChoice: selectedTeam,
143
+ amount: buyIn,
144
+ });
145
+ } catch {
146
+ // Error is already captured in mutation state
147
+ }
148
+ }, [selectedTeam, wallet.publicKey, mutation.execute, game.gameId, buyIn]); // eslint-disable-line react-hooks/exhaustive-deps
149
+
150
+ const statusLabel = STATUS_LABELS[mutation.status] || '';
151
+
152
+ return (
153
+ <Modal
154
+ visible={visible}
155
+ animationType="slide"
156
+ transparent
157
+ onRequestClose={onDismiss}
158
+ >
159
+ <Animated.View style={[styles.overlay, { opacity: overlayOpacity }]}>
160
+ <TouchableOpacity style={styles.overlayTap} activeOpacity={1} onPress={onDismiss} />
161
+ </Animated.View>
162
+
163
+ <KeyboardAvoidingView
164
+ style={styles.keyboardView}
165
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
166
+ >
167
+ <View style={styles.sheetPositioner}>
168
+ <View style={[styles.sheet, { backgroundColor: t.background }]}>
169
+ {/* Drag handle */}
170
+ <View style={styles.handleRow}>
171
+ <View style={[styles.handle, { backgroundColor: t.textMuted }]} />
172
+ </View>
173
+
174
+ {/* Header */}
175
+ <View style={styles.header}>
176
+ <Text style={[styles.headerTitle, { color: t.text }]}>Join Game</Text>
177
+ <TouchableOpacity onPress={onDismiss} activeOpacity={0.8}>
178
+ <Text style={[styles.closeButton, { color: t.textMuted }]}>{'\u2715'}</Text>
179
+ </TouchableOpacity>
180
+ </View>
181
+
182
+ {/* Team Selection — only for non-custom (sports/esports) games */}
183
+ {!isCustomGame && (
184
+ <View style={styles.section}>
185
+ <Text style={[styles.sectionLabel, { color: t.textSecondary }]}>Pick Your Side</Text>
186
+ <View style={styles.teamsRow}>
187
+ <TeamButton
188
+ name={homeName}
189
+ imageUrl={opponents[0]?.imageUrl}
190
+ odds={homeOdds}
191
+ bets={homeBets}
192
+ color={homeColor}
193
+ selected={selectedTeam === 'home'}
194
+ onPress={() => setSelectedTeam('home')}
195
+ ImageComponent={ImageComponent}
196
+ t={t}
197
+ />
198
+ <TeamButton
199
+ name={awayName}
200
+ imageUrl={opponents[1]?.imageUrl}
201
+ odds={awayOdds}
202
+ bets={awayBets}
203
+ color={awayColor}
204
+ selected={selectedTeam === 'away'}
205
+ onPress={() => setSelectedTeam('away')}
206
+ ImageComponent={ImageComponent}
207
+ t={t}
208
+ />
209
+ </View>
210
+ </View>
211
+ )}
212
+
213
+ {/* Summary Card */}
214
+ <View style={[styles.summaryCard, { backgroundColor: t.surface, borderColor: t.border }]}>
215
+ <View style={styles.summaryRow}>
216
+ <Text style={[styles.summaryLabel, { color: t.textMuted }]}>Buy-in</Text>
217
+ <Text style={[styles.summaryValue, { color: t.text }]}>{buyIn} SOL</Text>
218
+ </View>
219
+ <View style={[styles.summarySep, { backgroundColor: t.border }]} />
220
+ <View style={styles.summaryRow}>
221
+ <Text style={[styles.summaryLabel, { color: t.textMuted }]}>Your side</Text>
222
+ <Text style={[styles.summaryValue, { color: t.text }]}>{selectedName}</Text>
223
+ </View>
224
+ <View style={[styles.summarySep, { backgroundColor: t.border }]} />
225
+ <View style={styles.summaryRow}>
226
+ <Text style={[styles.summaryLabel, { color: t.textMuted }]}>Total pool</Text>
227
+ <Text style={[styles.summaryValue, { color: t.text }]}>{poolAfterJoin} SOL</Text>
228
+ </View>
229
+ <View style={[styles.summarySep, { backgroundColor: t.border }]} />
230
+ <View style={styles.summaryRow}>
231
+ <Text style={[styles.summaryLabel, { color: t.textMuted }]}>Potential winnings</Text>
232
+ <Text style={[styles.summaryValue, { color: t.success }]}>
233
+ {potentialWinnings !== '—' ? `${potentialWinnings} SOL` : '—'}
234
+ </Text>
235
+ </View>
236
+ </View>
237
+
238
+ {/* Already Joined Notice */}
239
+ {alreadyJoined && (
240
+ <View style={[styles.errorBox, { backgroundColor: t.surface, borderColor: t.border }]}>
241
+ <Text style={[styles.errorText, { color: t.textMuted }]}>You've already joined this game.</Text>
242
+ </View>
243
+ )}
244
+
245
+ {/* Error Display */}
246
+ {mutation.error && (
247
+ <View style={[styles.errorBox, { backgroundColor: t.errorBg, borderColor: t.errorBorder }]}>
248
+ <Text style={[styles.errorText, { color: t.errorText }]}>{mutation.error.message}</Text>
249
+ </View>
250
+ )}
251
+
252
+ {/* CTA Button */}
253
+ <TouchableOpacity
254
+ style={[
255
+ styles.ctaButton,
256
+ { backgroundColor: canJoin ? t.accent : t.border },
257
+ ]}
258
+ disabled={!canJoin}
259
+ onPress={handleJoin}
260
+ activeOpacity={0.8}
261
+ >
262
+ {isMutating ? (
263
+ <View style={styles.ctaLoading}>
264
+ <ActivityIndicator size="small" color="#FFFFFF" />
265
+ <Text style={styles.ctaText}>{statusLabel}</Text>
266
+ </View>
267
+ ) : mutation.status === 'success' ? (
268
+ <Text style={styles.ctaText}>{STATUS_LABELS.success}</Text>
269
+ ) : (
270
+ <Text style={[styles.ctaText, !canJoin && { opacity: 0.5 }]}>
271
+ {alreadyJoined
272
+ ? 'Already Joined'
273
+ : selectedTeam
274
+ ? `Join Game \u2014 ${buyIn} SOL`
275
+ : 'Pick a side to join'}
276
+ </Text>
277
+ )}
278
+ </TouchableOpacity>
279
+ </View>
280
+ </View>
281
+ </KeyboardAvoidingView>
282
+ </Modal>
283
+ );
284
+ }
285
+
286
+ // ── TeamButton (internal, for sports/esports games) ──
287
+
288
+ function TeamButton({
289
+ name, imageUrl, odds, bets, color, selected, onPress, ImageComponent, t,
290
+ }: {
291
+ name: string; imageUrl?: string | null; odds: string; bets: number;
292
+ color: string; selected: boolean; onPress: () => void;
293
+ ImageComponent?: React.ComponentType<any>; t: any;
294
+ }) {
295
+ const [imgFailed, setImgFailed] = useState(false);
296
+ const Img = ImageComponent || require('react-native').Image;
297
+ const showImage = imageUrl && !imgFailed;
298
+
299
+ return (
300
+ <TouchableOpacity
301
+ style={[styles.teamOption, { borderColor: selected ? color : t.border, backgroundColor: selected ? color + '15' : t.background }]}
302
+ onPress={onPress}
303
+ activeOpacity={0.7}
304
+ >
305
+ {showImage ? (
306
+ <Img source={{ uri: imageUrl }} style={styles.teamLogo} resizeMode="contain" onError={() => setImgFailed(true)} />
307
+ ) : (
308
+ <View style={[styles.teamLogo, styles.teamLogoPlaceholder]} />
309
+ )}
310
+ <Text style={[styles.teamName, { color: t.text }]} numberOfLines={1}>{name}</Text>
311
+ <Text style={[styles.teamOdds, { color }]}>{odds}x</Text>
312
+ <Text style={[styles.teamBets, { color: t.textMuted }]}>{bets} {bets === 1 ? 'bet' : 'bets'}</Text>
313
+ {selected && (
314
+ <View style={[styles.teamBadge, { backgroundColor: color }]}>
315
+ <Text style={styles.teamBadgeText}>Selected</Text>
316
+ </View>
317
+ )}
318
+ </TouchableOpacity>
319
+ );
320
+ }
321
+
322
+ const styles = StyleSheet.create({
323
+ overlay: {
324
+ ...StyleSheet.absoluteFillObject,
325
+ backgroundColor: 'rgba(0,0,0,0.5)',
326
+ },
327
+ overlayTap: {
328
+ flex: 1,
329
+ },
330
+ keyboardView: {
331
+ flex: 1,
332
+ justifyContent: 'flex-end',
333
+ },
334
+ sheetPositioner: {
335
+ justifyContent: 'flex-end',
336
+ },
337
+ sheet: {
338
+ borderTopLeftRadius: 24,
339
+ borderTopRightRadius: 24,
340
+ paddingHorizontal: 20,
341
+ paddingBottom: 40,
342
+ },
343
+ handleRow: {
344
+ alignItems: 'center',
345
+ paddingTop: 10,
346
+ paddingBottom: 8,
347
+ },
348
+ handle: {
349
+ width: 36,
350
+ height: 4,
351
+ borderRadius: 2,
352
+ opacity: 0.4,
353
+ },
354
+ header: {
355
+ flexDirection: 'row',
356
+ alignItems: 'center',
357
+ justifyContent: 'space-between',
358
+ paddingVertical: 12,
359
+ },
360
+ headerTitle: {
361
+ fontSize: 20,
362
+ fontWeight: '700',
363
+ },
364
+ closeButton: {
365
+ fontSize: 20,
366
+ padding: 4,
367
+ },
368
+ section: {
369
+ gap: 12,
370
+ paddingTop: 8,
371
+ },
372
+ sectionLabel: {
373
+ fontSize: 15,
374
+ fontWeight: '600',
375
+ },
376
+ teamsRow: {
377
+ flexDirection: 'row',
378
+ gap: 12,
379
+ },
380
+ summaryCard: {
381
+ marginTop: 20,
382
+ borderRadius: 16,
383
+ borderWidth: 1,
384
+ overflow: 'hidden',
385
+ },
386
+ summaryRow: {
387
+ flexDirection: 'row',
388
+ alignItems: 'center',
389
+ justifyContent: 'space-between',
390
+ paddingHorizontal: 16,
391
+ paddingVertical: 14,
392
+ },
393
+ summaryLabel: {
394
+ fontSize: 14,
395
+ },
396
+ summaryValue: {
397
+ fontSize: 15,
398
+ fontWeight: '700',
399
+ },
400
+ summarySep: {
401
+ height: 1,
402
+ marginHorizontal: 16,
403
+ },
404
+ errorBox: {
405
+ marginTop: 16,
406
+ borderRadius: 12,
407
+ borderWidth: 1,
408
+ padding: 12,
409
+ },
410
+ errorText: {
411
+ fontSize: 13,
412
+ fontWeight: '500',
413
+ },
414
+ ctaButton: {
415
+ marginTop: 20,
416
+ height: 56,
417
+ borderRadius: 14,
418
+ justifyContent: 'center',
419
+ alignItems: 'center',
420
+ },
421
+ ctaText: {
422
+ color: '#FFFFFF',
423
+ fontSize: 16,
424
+ fontWeight: '700',
425
+ },
426
+ ctaLoading: {
427
+ flexDirection: 'row',
428
+ alignItems: 'center',
429
+ gap: 10,
430
+ },
431
+ // Team button styles
432
+ teamOption: {
433
+ flex: 1,
434
+ borderWidth: 2,
435
+ borderRadius: 16,
436
+ padding: 16,
437
+ alignItems: 'center',
438
+ gap: 8,
439
+ },
440
+ teamLogo: {
441
+ width: 48,
442
+ height: 48,
443
+ borderRadius: 24,
444
+ },
445
+ teamLogoPlaceholder: {
446
+ backgroundColor: 'rgba(128,128,128,0.2)',
447
+ },
448
+ teamName: {
449
+ fontSize: 15,
450
+ fontWeight: '700',
451
+ },
452
+ teamOdds: {
453
+ fontSize: 20,
454
+ fontWeight: '800',
455
+ },
456
+ teamBets: {
457
+ fontSize: 12,
458
+ },
459
+ teamBadge: {
460
+ borderRadius: 8,
461
+ paddingHorizontal: 12,
462
+ paddingVertical: 4,
463
+ marginTop: 4,
464
+ },
465
+ teamBadgeText: {
466
+ color: '#FFF',
467
+ fontSize: 12,
468
+ fontWeight: '700',
469
+ },
470
+ });
@@ -10,3 +10,5 @@ export { JoinGameButton } from './JoinGameButton';
10
10
  export type { JoinGameButtonProps } from './JoinGameButton';
11
11
  export { CreateCustomGameSheet } from './CreateCustomGameSheet';
12
12
  export type { CreateCustomGameSheetProps } from './CreateCustomGameSheet';
13
+ export { JoinGameSheet } from './JoinGameSheet';
14
+ export type { JoinGameSheetProps } from './JoinGameSheet';
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 } from './game';
13
+ export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet } from './game';
14
14
  export type {
15
15
  GamePosterProps,
16
16
  LivePoolsCardProps,
@@ -18,4 +18,5 @@ export type {
18
18
  PlayersCardProps,
19
19
  JoinGameButtonProps,
20
20
  CreateCustomGameSheetProps,
21
+ JoinGameSheetProps,
21
22
  } from './game';