@dubsdotapp/expo 0.2.79 → 0.3.0

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.79",
3
+ "version": "0.3.0",
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
@@ -37,6 +37,8 @@ import type {
37
37
  UiConfig,
38
38
  UFCEvent,
39
39
  UFCFighterDetail,
40
+ DeveloperCommissionsResult,
41
+ DeveloperCommissionsSummary,
40
42
  } from './types';
41
43
 
42
44
  export interface DubsClientConfig {
@@ -482,4 +484,32 @@ export class DubsClient {
482
484
  }
483
485
  return config;
484
486
  }
487
+
488
+ // ── Developer Commissions ──
489
+
490
+ /** Fetch paginated list of commission earnings for this app */
491
+ async getCommissions(params?: { limit?: number; offset?: number }): Promise<DeveloperCommissionsResult> {
492
+ const qs = new URLSearchParams();
493
+ if (params?.limit != null) qs.set('limit', String(params.limit));
494
+ if (params?.offset != null) qs.set('offset', String(params.offset));
495
+ const query = qs.toString();
496
+ const res = await this.request<{ success: true } & DeveloperCommissionsResult>(
497
+ 'GET',
498
+ `/commissions${query ? `?${query}` : ''}`,
499
+ );
500
+ return {
501
+ commissions: res.commissions,
502
+ summary: res.summary,
503
+ pagination: res.pagination,
504
+ };
505
+ }
506
+
507
+ /** Fetch a quick summary of commission earnings for this app */
508
+ async getCommissionsSummary(): Promise<DeveloperCommissionsSummary> {
509
+ const res = await this.request<{ success: true; summary: DeveloperCommissionsSummary }>(
510
+ 'GET',
511
+ '/commissions/summary',
512
+ );
513
+ return res.summary;
514
+ }
485
515
  }
package/src/index.ts CHANGED
@@ -67,6 +67,9 @@ export type {
67
67
  UFCData,
68
68
  UFCFight,
69
69
  UFCEvent,
70
+ DeveloperCommission,
71
+ DeveloperCommissionsSummary,
72
+ DeveloperCommissionsResult,
70
73
  } from './types';
71
74
 
72
75
  // Provider
@@ -107,8 +110,8 @@ export type {
107
110
  } from './hooks';
108
111
 
109
112
  // UI
110
- export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, UserProfileSheet, useDubsTheme, mergeTheme } from './ui';
111
- export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, UserProfileSheetProps, DubsTheme } from './ui';
113
+ export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, UserProfileSheet, DeveloperDashboardSheet, useDubsTheme, mergeTheme } from './ui';
114
+ export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, UserProfileSheetProps, DeveloperDashboardSheetProps, DubsTheme } from './ui';
112
115
 
113
116
  // Game widgets
114
117
  export { GamePoster, LivePoolsCard, PickWinnerCard, PlayersCard, JoinGameButton, CreateCustomGameSheet, JoinGameSheet, ClaimPrizeSheet, ClaimButton } from './ui';
package/src/types.ts CHANGED
@@ -481,6 +481,59 @@ export interface UFCEvent {
481
481
  fights: UFCFight[];
482
482
  }
483
483
 
484
+ // ── Developer Commissions ──
485
+
486
+ export interface DeveloperCommission {
487
+ id: number;
488
+ gameId: string;
489
+ /** Commission amount in lamports */
490
+ amount: number;
491
+ /** Commission amount in SOL */
492
+ amountSol: number;
493
+ status: 'pending' | 'paid' | 'cancelled';
494
+ createdAt: string;
495
+ paidAt: string | null;
496
+ txSignature: string | null;
497
+ gameType: string;
498
+ /** Total pot size in lamports */
499
+ potSize: number;
500
+ /** Total pot size in SOL */
501
+ potSizeSol: number;
502
+ appId?: number;
503
+ appName?: string;
504
+ }
505
+
506
+ export interface DeveloperCommissionsSummary {
507
+ totalEarned: number;
508
+ totalEarnedSol: number;
509
+ totalPaid: number;
510
+ totalPaidSol: number;
511
+ totalPending: number;
512
+ totalPendingSol: number;
513
+ totalGames: number;
514
+ last7Days?: {
515
+ games: number;
516
+ earned: number;
517
+ earnedSol: number;
518
+ };
519
+ last30Days?: {
520
+ games: number;
521
+ earned: number;
522
+ earnedSol: number;
523
+ };
524
+ commissionWallet?: string;
525
+ }
526
+
527
+ export interface DeveloperCommissionsResult {
528
+ commissions: DeveloperCommission[];
529
+ summary: DeveloperCommissionsSummary;
530
+ pagination: {
531
+ total: number;
532
+ limit: number;
533
+ offset: number;
534
+ };
535
+ }
536
+
484
537
  // ── UI Config (developer branding) ──
485
538
 
486
539
  export interface UiConfig {
@@ -0,0 +1,353 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Animated,
5
+ FlatList,
6
+ KeyboardAvoidingView,
7
+ Modal,
8
+ Platform,
9
+ StyleSheet,
10
+ Text,
11
+ TouchableOpacity,
12
+ View,
13
+ } from 'react-native';
14
+ import type { DeveloperCommission, DeveloperCommissionsSummary } from '../types';
15
+ import { useDubs } from '../provider';
16
+ import { useDubsTheme } from './theme';
17
+
18
+ export interface DeveloperDashboardSheetProps {
19
+ visible: boolean;
20
+ onDismiss: () => void;
21
+ }
22
+
23
+ export function DeveloperDashboardSheet({ visible, onDismiss }: DeveloperDashboardSheetProps) {
24
+ const t = useDubsTheme();
25
+ const { client } = useDubs();
26
+
27
+ const [summary, setSummary] = useState<DeveloperCommissionsSummary | null>(null);
28
+ const [commissions, setCommissions] = useState<DeveloperCommission[]>([]);
29
+ const [loading, setLoading] = useState(false);
30
+ const [error, setError] = useState<string | null>(null);
31
+
32
+ const overlayOpacity = useRef(new Animated.Value(0)).current;
33
+
34
+ useEffect(() => {
35
+ Animated.timing(overlayOpacity, {
36
+ toValue: visible ? 1 : 0,
37
+ duration: 250,
38
+ useNativeDriver: true,
39
+ }).start();
40
+ }, [visible, overlayOpacity]);
41
+
42
+ const fetchData = useCallback(async () => {
43
+ setLoading(true);
44
+ setError(null);
45
+ try {
46
+ const result = await client.getCommissions({ limit: 50 });
47
+ setCommissions(result.commissions);
48
+ setSummary(result.summary);
49
+ } catch (err) {
50
+ setError(err instanceof Error ? err.message : 'Failed to load commissions');
51
+ } finally {
52
+ setLoading(false);
53
+ }
54
+ }, [client]);
55
+
56
+ useEffect(() => {
57
+ if (visible) fetchData();
58
+ }, [visible, fetchData]);
59
+
60
+ const formatSol = (sol: number) => {
61
+ if (sol === 0) return '0';
62
+ if (sol < 0.001) return '<0.001';
63
+ return sol.toFixed(3);
64
+ };
65
+
66
+ const renderCommission = ({ item }: { item: DeveloperCommission }) => (
67
+ <View style={[styles.row, { backgroundColor: t.surface, borderColor: t.border }]}>
68
+ <View style={styles.rowTop}>
69
+ <Text style={[styles.rowGameId, { color: t.text }]} numberOfLines={1}>
70
+ {item.gameId.slice(0, 16)}...
71
+ </Text>
72
+ <Text style={[styles.rowAmount, { color: t.success }]}>
73
+ +{formatSol(item.amountSol)} SOL
74
+ </Text>
75
+ </View>
76
+ <View style={styles.rowBottom}>
77
+ <Text style={[styles.rowMeta, { color: t.textMuted }]}>
78
+ {item.gameType || 'game'} | pot {formatSol(item.potSizeSol)} SOL
79
+ </Text>
80
+ <View style={[
81
+ styles.statusBadge,
82
+ { backgroundColor: item.status === 'paid' ? t.success + '22' : t.accent + '22' },
83
+ ]}>
84
+ <Text style={[
85
+ styles.statusText,
86
+ { color: item.status === 'paid' ? t.success : t.accent },
87
+ ]}>
88
+ {item.status}
89
+ </Text>
90
+ </View>
91
+ </View>
92
+ <Text style={[styles.rowDate, { color: t.textDim }]}>
93
+ {new Date(item.createdAt).toLocaleDateString()}
94
+ </Text>
95
+ </View>
96
+ );
97
+
98
+ return (
99
+ <Modal visible={visible} animationType="slide" transparent onRequestClose={onDismiss}>
100
+ <Animated.View style={[styles.overlay, { opacity: overlayOpacity }]} />
101
+ <KeyboardAvoidingView
102
+ style={styles.flex}
103
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
104
+ >
105
+ <View style={styles.flex} />
106
+ <View style={[styles.sheet, { backgroundColor: t.background }]}>
107
+ {/* Drag handle */}
108
+ <View style={styles.handleRow}>
109
+ <View style={[styles.handle, { backgroundColor: t.textMuted }]} />
110
+ </View>
111
+
112
+ {/* Header */}
113
+ <View style={styles.header}>
114
+ <Text style={[styles.headerTitle, { color: t.text }]}>Commission Earnings</Text>
115
+ <TouchableOpacity onPress={onDismiss} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}>
116
+ <Text style={[styles.closeButton, { color: t.textMuted }]}>{'\u2715'}</Text>
117
+ </TouchableOpacity>
118
+ </View>
119
+
120
+ {/* Summary cards */}
121
+ {summary && (
122
+ <View style={styles.summaryRow}>
123
+ <View style={[styles.summaryCard, { backgroundColor: t.surface }]}>
124
+ <Text style={[styles.summaryLabel, { color: t.textMuted }]}>Total Earned</Text>
125
+ <Text style={[styles.summaryValue, { color: t.success }]}>
126
+ {formatSol(summary.totalEarnedSol)} SOL
127
+ </Text>
128
+ </View>
129
+ <View style={[styles.summaryCard, { backgroundColor: t.surface }]}>
130
+ <Text style={[styles.summaryLabel, { color: t.textMuted }]}>Games</Text>
131
+ <Text style={[styles.summaryValue, { color: t.text }]}>
132
+ {summary.totalGames}
133
+ </Text>
134
+ </View>
135
+ <View style={[styles.summaryCard, { backgroundColor: t.surface }]}>
136
+ <Text style={[styles.summaryLabel, { color: t.textMuted }]}>Paid</Text>
137
+ <Text style={[styles.summaryValue, { color: t.accent }]}>
138
+ {formatSol(summary.totalPaidSol)} SOL
139
+ </Text>
140
+ </View>
141
+ </View>
142
+ )}
143
+
144
+ {summary?.commissionWallet && (
145
+ <View style={[styles.walletRow, { backgroundColor: t.surface, borderColor: t.border }]}>
146
+ <Text style={[styles.walletLabel, { color: t.textMuted }]}>Commission Wallet</Text>
147
+ <Text style={[styles.walletAddress, { color: t.textSecondary }]} numberOfLines={1}>
148
+ {summary.commissionWallet}
149
+ </Text>
150
+ </View>
151
+ )}
152
+
153
+ {/* Content */}
154
+ {loading && !commissions.length ? (
155
+ <View style={styles.centered}>
156
+ <ActivityIndicator size="large" color={t.accent} />
157
+ </View>
158
+ ) : error ? (
159
+ <View style={[styles.errorBox, { backgroundColor: t.errorBg, borderColor: t.errorBorder }]}>
160
+ <Text style={[styles.errorText, { color: t.errorText }]}>{error}</Text>
161
+ <TouchableOpacity onPress={fetchData} style={[styles.retryButton, { borderColor: t.errorBorder }]}>
162
+ <Text style={[styles.retryText, { color: t.errorText }]}>Retry</Text>
163
+ </TouchableOpacity>
164
+ </View>
165
+ ) : commissions.length === 0 ? (
166
+ <View style={styles.centered}>
167
+ <Text style={[styles.emptyTitle, { color: t.textMuted }]}>No commissions yet</Text>
168
+ <Text style={[styles.emptySubtitle, { color: t.textDim }]}>
169
+ When games created through your app are resolved, your 1% commission will appear here.
170
+ </Text>
171
+ </View>
172
+ ) : (
173
+ <FlatList
174
+ data={commissions}
175
+ keyExtractor={(item) => String(item.id)}
176
+ renderItem={renderCommission}
177
+ contentContainerStyle={styles.listContent}
178
+ showsVerticalScrollIndicator={false}
179
+ />
180
+ )}
181
+ </View>
182
+ </KeyboardAvoidingView>
183
+ </Modal>
184
+ );
185
+ }
186
+
187
+ const styles = StyleSheet.create({
188
+ flex: { flex: 1 },
189
+ overlay: {
190
+ ...StyleSheet.absoluteFillObject,
191
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
192
+ },
193
+ sheet: {
194
+ borderTopLeftRadius: 20,
195
+ borderTopRightRadius: 20,
196
+ maxHeight: '85%',
197
+ minHeight: 400,
198
+ paddingBottom: 34,
199
+ },
200
+ handleRow: {
201
+ alignItems: 'center',
202
+ paddingTop: 10,
203
+ paddingBottom: 6,
204
+ },
205
+ handle: {
206
+ width: 36,
207
+ height: 4,
208
+ borderRadius: 2,
209
+ opacity: 0.4,
210
+ },
211
+ header: {
212
+ flexDirection: 'row',
213
+ alignItems: 'center',
214
+ justifyContent: 'space-between',
215
+ paddingHorizontal: 20,
216
+ paddingVertical: 12,
217
+ },
218
+ headerTitle: {
219
+ fontSize: 18,
220
+ fontWeight: '700',
221
+ },
222
+ closeButton: {
223
+ fontSize: 18,
224
+ fontWeight: '600',
225
+ },
226
+ summaryRow: {
227
+ flexDirection: 'row',
228
+ paddingHorizontal: 16,
229
+ gap: 8,
230
+ marginBottom: 12,
231
+ },
232
+ summaryCard: {
233
+ flex: 1,
234
+ borderRadius: 12,
235
+ padding: 12,
236
+ alignItems: 'center',
237
+ },
238
+ summaryLabel: {
239
+ fontSize: 11,
240
+ fontWeight: '600',
241
+ textTransform: 'uppercase',
242
+ letterSpacing: 0.5,
243
+ marginBottom: 4,
244
+ },
245
+ summaryValue: {
246
+ fontSize: 16,
247
+ fontWeight: '700',
248
+ },
249
+ walletRow: {
250
+ marginHorizontal: 16,
251
+ borderRadius: 10,
252
+ borderWidth: 1,
253
+ padding: 10,
254
+ marginBottom: 12,
255
+ },
256
+ walletLabel: {
257
+ fontSize: 11,
258
+ fontWeight: '600',
259
+ textTransform: 'uppercase',
260
+ letterSpacing: 0.5,
261
+ marginBottom: 2,
262
+ },
263
+ walletAddress: {
264
+ fontSize: 12,
265
+ fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
266
+ },
267
+ centered: {
268
+ flex: 1,
269
+ justifyContent: 'center',
270
+ alignItems: 'center',
271
+ paddingHorizontal: 32,
272
+ paddingVertical: 48,
273
+ },
274
+ emptyTitle: {
275
+ fontSize: 16,
276
+ fontWeight: '600',
277
+ marginBottom: 8,
278
+ },
279
+ emptySubtitle: {
280
+ fontSize: 13,
281
+ textAlign: 'center',
282
+ lineHeight: 18,
283
+ },
284
+ errorBox: {
285
+ margin: 16,
286
+ borderRadius: 10,
287
+ borderWidth: 1,
288
+ padding: 16,
289
+ alignItems: 'center',
290
+ },
291
+ errorText: {
292
+ fontSize: 14,
293
+ marginBottom: 12,
294
+ },
295
+ retryButton: {
296
+ borderWidth: 1,
297
+ borderRadius: 8,
298
+ paddingHorizontal: 16,
299
+ paddingVertical: 6,
300
+ },
301
+ retryText: {
302
+ fontSize: 13,
303
+ fontWeight: '600',
304
+ },
305
+ listContent: {
306
+ paddingHorizontal: 16,
307
+ paddingBottom: 16,
308
+ },
309
+ row: {
310
+ borderRadius: 12,
311
+ borderWidth: 1,
312
+ padding: 12,
313
+ marginBottom: 8,
314
+ },
315
+ rowTop: {
316
+ flexDirection: 'row',
317
+ justifyContent: 'space-between',
318
+ alignItems: 'center',
319
+ marginBottom: 4,
320
+ },
321
+ rowGameId: {
322
+ fontSize: 14,
323
+ fontWeight: '600',
324
+ flex: 1,
325
+ marginRight: 8,
326
+ },
327
+ rowAmount: {
328
+ fontSize: 14,
329
+ fontWeight: '700',
330
+ },
331
+ rowBottom: {
332
+ flexDirection: 'row',
333
+ justifyContent: 'space-between',
334
+ alignItems: 'center',
335
+ marginBottom: 2,
336
+ },
337
+ rowMeta: {
338
+ fontSize: 12,
339
+ },
340
+ statusBadge: {
341
+ borderRadius: 6,
342
+ paddingHorizontal: 8,
343
+ paddingVertical: 2,
344
+ },
345
+ statusText: {
346
+ fontSize: 11,
347
+ fontWeight: '600',
348
+ textTransform: 'uppercase',
349
+ },
350
+ rowDate: {
351
+ fontSize: 11,
352
+ },
353
+ });
package/src/ui/index.ts CHANGED
@@ -8,6 +8,8 @@ export { SettingsSheet } from './SettingsSheet';
8
8
  export type { SettingsSheetProps } from './SettingsSheet';
9
9
  export { UserProfileSheet } from './UserProfileSheet';
10
10
  export type { UserProfileSheetProps } from './UserProfileSheet';
11
+ export { DeveloperDashboardSheet } from './DeveloperDashboardSheet';
12
+ export type { DeveloperDashboardSheetProps } from './DeveloperDashboardSheet';
11
13
  export { useDubsTheme, mergeTheme } from './theme';
12
14
  export type { DubsTheme } from './theme';
13
15