@dubsdotapp/expo 0.1.0 → 0.1.2
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/README.md +9 -10
- package/dist/index.d.mts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +432 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +442 -55
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/ui/AuthGate.tsx +520 -0
- package/src/ui/index.ts +2 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TextInput,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
ActivityIndicator,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
Keyboard,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { useAuth } from '../hooks';
|
|
12
|
+
import { useDubs } from '../provider';
|
|
13
|
+
import { useDubsTheme } from './theme';
|
|
14
|
+
import type { AuthStatus } from '../types';
|
|
15
|
+
import type { DubsClient } from '../client';
|
|
16
|
+
|
|
17
|
+
// ── Public Types ──
|
|
18
|
+
|
|
19
|
+
export interface RegistrationScreenProps {
|
|
20
|
+
/** Called when the user submits registration */
|
|
21
|
+
onRegister: (username: string, referralCode?: string) => void;
|
|
22
|
+
/** Whether a registration request is in flight */
|
|
23
|
+
registering: boolean;
|
|
24
|
+
/** Error from the last registration attempt, or null */
|
|
25
|
+
error: Error | null;
|
|
26
|
+
/** DubsClient instance for checkUsername() availability checks */
|
|
27
|
+
client: DubsClient;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AuthGateProps {
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
/** Persist or clear the JWT token (called with null on logout) */
|
|
33
|
+
onSaveToken: (token: string | null) => void | Promise<void>;
|
|
34
|
+
/** Load a previously persisted JWT token */
|
|
35
|
+
onLoadToken: () => string | null | Promise<string | null>;
|
|
36
|
+
/** Custom loading screen (receives current auth status) */
|
|
37
|
+
renderLoading?: (status: AuthStatus) => React.ReactNode;
|
|
38
|
+
/** Custom error screen (receives the error and a retry callback) */
|
|
39
|
+
renderError?: (error: Error, retry: () => void) => React.ReactNode;
|
|
40
|
+
/** Custom registration screen */
|
|
41
|
+
renderRegistration?: (props: RegistrationScreenProps) => React.ReactNode;
|
|
42
|
+
/** App name shown in default screens. Defaults to "Dubs" */
|
|
43
|
+
appName?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── AuthGate Component ──
|
|
47
|
+
|
|
48
|
+
export function AuthGate({
|
|
49
|
+
children,
|
|
50
|
+
onSaveToken,
|
|
51
|
+
onLoadToken,
|
|
52
|
+
renderLoading,
|
|
53
|
+
renderError,
|
|
54
|
+
renderRegistration,
|
|
55
|
+
appName = 'Dubs',
|
|
56
|
+
}: AuthGateProps) {
|
|
57
|
+
const { client } = useDubs();
|
|
58
|
+
const auth = useAuth();
|
|
59
|
+
const [phase, setPhase] = useState<'init' | 'active'>('init');
|
|
60
|
+
const [registrationPhase, setRegistrationPhase] = useState(false);
|
|
61
|
+
|
|
62
|
+
// Kick off the auth flow on mount
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
let cancelled = false;
|
|
65
|
+
|
|
66
|
+
(async () => {
|
|
67
|
+
try {
|
|
68
|
+
const savedToken = await onLoadToken();
|
|
69
|
+
if (cancelled) return;
|
|
70
|
+
|
|
71
|
+
if (savedToken) {
|
|
72
|
+
const restored = await auth.restoreSession(savedToken);
|
|
73
|
+
if (cancelled) return;
|
|
74
|
+
if (restored) {
|
|
75
|
+
setPhase('active');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Token invalid — clear it
|
|
79
|
+
await onSaveToken(null);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (cancelled) return;
|
|
83
|
+
setPhase('active');
|
|
84
|
+
// No valid session — start fresh authentication
|
|
85
|
+
await auth.authenticate();
|
|
86
|
+
} catch {
|
|
87
|
+
if (!cancelled) setPhase('active');
|
|
88
|
+
}
|
|
89
|
+
})();
|
|
90
|
+
|
|
91
|
+
return () => {
|
|
92
|
+
cancelled = true;
|
|
93
|
+
};
|
|
94
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
// Track when we enter the registration phase
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (auth.status === 'needsRegistration') {
|
|
100
|
+
setRegistrationPhase(true);
|
|
101
|
+
}
|
|
102
|
+
}, [auth.status]);
|
|
103
|
+
|
|
104
|
+
// Persist token when authentication or registration completes
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (auth.token) {
|
|
107
|
+
onSaveToken(auth.token);
|
|
108
|
+
}
|
|
109
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
110
|
+
}, [auth.token]);
|
|
111
|
+
|
|
112
|
+
const retry = useCallback(() => {
|
|
113
|
+
setRegistrationPhase(false);
|
|
114
|
+
auth.reset();
|
|
115
|
+
auth.authenticate();
|
|
116
|
+
}, [auth]);
|
|
117
|
+
|
|
118
|
+
const handleRegister = useCallback(
|
|
119
|
+
(username: string, referralCode?: string) => {
|
|
120
|
+
auth.register(username, referralCode);
|
|
121
|
+
},
|
|
122
|
+
[auth],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// ── Render Logic ──
|
|
126
|
+
|
|
127
|
+
// Phase 1: Loading saved token
|
|
128
|
+
if (phase === 'init') {
|
|
129
|
+
if (renderLoading) return <>{renderLoading('authenticating')}</>;
|
|
130
|
+
return <DefaultLoadingScreen status="authenticating" appName={appName} />;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Authenticated — render app
|
|
134
|
+
if (auth.status === 'authenticated') {
|
|
135
|
+
return <>{children}</>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Registration flow (including errors during registration)
|
|
139
|
+
if (registrationPhase) {
|
|
140
|
+
const isRegistering = auth.status === 'registering';
|
|
141
|
+
const regError = auth.status === 'error' ? auth.error : null;
|
|
142
|
+
|
|
143
|
+
if (renderRegistration) {
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
{renderRegistration({
|
|
147
|
+
onRegister: handleRegister,
|
|
148
|
+
registering: isRegistering,
|
|
149
|
+
error: regError,
|
|
150
|
+
client,
|
|
151
|
+
})}
|
|
152
|
+
</>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return (
|
|
156
|
+
<DefaultRegistrationScreen
|
|
157
|
+
onRegister={handleRegister}
|
|
158
|
+
registering={isRegistering}
|
|
159
|
+
error={regError}
|
|
160
|
+
client={client}
|
|
161
|
+
appName={appName}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Error (non-registration)
|
|
167
|
+
if (auth.status === 'error' && auth.error) {
|
|
168
|
+
if (renderError) return <>{renderError(auth.error, retry)}</>;
|
|
169
|
+
return <DefaultErrorScreen error={auth.error} onRetry={retry} appName={appName} />;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Loading states: idle, authenticating, signing, verifying
|
|
173
|
+
if (renderLoading) return <>{renderLoading(auth.status)}</>;
|
|
174
|
+
return <DefaultLoadingScreen status={auth.status} appName={appName} />;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Default Loading Screen ──
|
|
178
|
+
|
|
179
|
+
function DefaultLoadingScreen({
|
|
180
|
+
status,
|
|
181
|
+
appName,
|
|
182
|
+
}: {
|
|
183
|
+
status: AuthStatus;
|
|
184
|
+
appName: string;
|
|
185
|
+
}) {
|
|
186
|
+
const t = useDubsTheme();
|
|
187
|
+
|
|
188
|
+
const statusText: Record<AuthStatus, string> = {
|
|
189
|
+
idle: 'Initializing...',
|
|
190
|
+
authenticating: 'Connecting...',
|
|
191
|
+
signing: 'Approve in your wallet...',
|
|
192
|
+
verifying: 'Verifying...',
|
|
193
|
+
registering: 'Creating account...',
|
|
194
|
+
needsRegistration: 'Almost there...',
|
|
195
|
+
authenticated: 'Ready!',
|
|
196
|
+
error: 'Something went wrong',
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<View style={[styles.container, { backgroundColor: t.background }]}>
|
|
201
|
+
<View style={styles.centerContent}>
|
|
202
|
+
<View style={styles.brandingSection}>
|
|
203
|
+
<View style={[styles.logoCircle, { backgroundColor: t.accent }]}>
|
|
204
|
+
<Text style={styles.logoText}>D</Text>
|
|
205
|
+
</View>
|
|
206
|
+
<Text style={[styles.appName, { color: t.text }]}>{appName}</Text>
|
|
207
|
+
</View>
|
|
208
|
+
<View style={styles.loadingSection}>
|
|
209
|
+
<ActivityIndicator size="large" color={t.accent} />
|
|
210
|
+
<Text style={[styles.statusText, { color: t.textMuted }]}>
|
|
211
|
+
{statusText[status] || 'Loading...'}
|
|
212
|
+
</Text>
|
|
213
|
+
</View>
|
|
214
|
+
</View>
|
|
215
|
+
</View>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Default Error Screen ──
|
|
220
|
+
|
|
221
|
+
function DefaultErrorScreen({
|
|
222
|
+
error,
|
|
223
|
+
onRetry,
|
|
224
|
+
appName,
|
|
225
|
+
}: {
|
|
226
|
+
error: Error;
|
|
227
|
+
onRetry: () => void;
|
|
228
|
+
appName: string;
|
|
229
|
+
}) {
|
|
230
|
+
const t = useDubsTheme();
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<View style={[styles.container, { backgroundColor: t.background }]}>
|
|
234
|
+
<View style={styles.spreadContent}>
|
|
235
|
+
<View style={styles.brandingSection}>
|
|
236
|
+
<View style={[styles.logoCircle, { backgroundColor: t.accent }]}>
|
|
237
|
+
<Text style={styles.logoText}>D</Text>
|
|
238
|
+
</View>
|
|
239
|
+
<Text style={[styles.appName, { color: t.text }]}>{appName}</Text>
|
|
240
|
+
</View>
|
|
241
|
+
<View style={styles.actionSection}>
|
|
242
|
+
<View
|
|
243
|
+
style={[
|
|
244
|
+
styles.errorBox,
|
|
245
|
+
{ backgroundColor: t.errorBg, borderColor: t.errorBorder },
|
|
246
|
+
]}
|
|
247
|
+
>
|
|
248
|
+
<Text style={[styles.errorText, { color: t.errorText }]}>
|
|
249
|
+
{error.message}
|
|
250
|
+
</Text>
|
|
251
|
+
</View>
|
|
252
|
+
<TouchableOpacity
|
|
253
|
+
style={[styles.button, { backgroundColor: t.accent }]}
|
|
254
|
+
onPress={onRetry}
|
|
255
|
+
activeOpacity={0.8}
|
|
256
|
+
>
|
|
257
|
+
<Text style={styles.buttonText}>Try Again</Text>
|
|
258
|
+
</TouchableOpacity>
|
|
259
|
+
</View>
|
|
260
|
+
</View>
|
|
261
|
+
</View>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── Default Registration Screen ──
|
|
266
|
+
|
|
267
|
+
function DefaultRegistrationScreen({
|
|
268
|
+
onRegister,
|
|
269
|
+
registering,
|
|
270
|
+
error,
|
|
271
|
+
client,
|
|
272
|
+
appName,
|
|
273
|
+
}: RegistrationScreenProps & { appName: string }) {
|
|
274
|
+
const t = useDubsTheme();
|
|
275
|
+
const [username, setUsername] = useState('');
|
|
276
|
+
const [referralCode, setReferralCode] = useState('');
|
|
277
|
+
const [checking, setChecking] = useState(false);
|
|
278
|
+
const [availability, setAvailability] = useState<{
|
|
279
|
+
available: boolean;
|
|
280
|
+
reason?: string;
|
|
281
|
+
} | null>(null);
|
|
282
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
283
|
+
|
|
284
|
+
// Debounced username availability check
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
287
|
+
|
|
288
|
+
const trimmed = username.trim();
|
|
289
|
+
if (trimmed.length < 3) {
|
|
290
|
+
setAvailability(null);
|
|
291
|
+
setChecking(false);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
setChecking(true);
|
|
296
|
+
debounceRef.current = setTimeout(async () => {
|
|
297
|
+
try {
|
|
298
|
+
const result = await client.checkUsername(trimmed);
|
|
299
|
+
setAvailability(result);
|
|
300
|
+
} catch {
|
|
301
|
+
setAvailability(null);
|
|
302
|
+
} finally {
|
|
303
|
+
setChecking(false);
|
|
304
|
+
}
|
|
305
|
+
}, 500);
|
|
306
|
+
|
|
307
|
+
return () => {
|
|
308
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
309
|
+
};
|
|
310
|
+
}, [username, client]);
|
|
311
|
+
|
|
312
|
+
const canSubmit =
|
|
313
|
+
username.trim().length >= 3 &&
|
|
314
|
+
availability?.available === true &&
|
|
315
|
+
!registering &&
|
|
316
|
+
!checking;
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<View style={[styles.container, { backgroundColor: t.background }]}>
|
|
320
|
+
<View style={styles.spreadContent}>
|
|
321
|
+
<View style={styles.brandingSection}>
|
|
322
|
+
<View style={[styles.logoCircle, { backgroundColor: t.accent }]}>
|
|
323
|
+
<Text style={styles.logoText}>D</Text>
|
|
324
|
+
</View>
|
|
325
|
+
<Text style={[styles.appName, { color: t.text }]}>{appName}</Text>
|
|
326
|
+
<Text style={[styles.subtitle, { color: t.textMuted }]}>
|
|
327
|
+
Choose a username to get started
|
|
328
|
+
</Text>
|
|
329
|
+
</View>
|
|
330
|
+
|
|
331
|
+
<View style={styles.actionSection}>
|
|
332
|
+
{error ? (
|
|
333
|
+
<View
|
|
334
|
+
style={[
|
|
335
|
+
styles.errorBox,
|
|
336
|
+
{ backgroundColor: t.errorBg, borderColor: t.errorBorder },
|
|
337
|
+
]}
|
|
338
|
+
>
|
|
339
|
+
<Text style={[styles.errorText, { color: t.errorText }]}>
|
|
340
|
+
{error.message}
|
|
341
|
+
</Text>
|
|
342
|
+
</View>
|
|
343
|
+
) : null}
|
|
344
|
+
|
|
345
|
+
<View>
|
|
346
|
+
<TextInput
|
|
347
|
+
style={[
|
|
348
|
+
styles.input,
|
|
349
|
+
{
|
|
350
|
+
backgroundColor: t.surface,
|
|
351
|
+
color: t.text,
|
|
352
|
+
borderColor: t.border,
|
|
353
|
+
},
|
|
354
|
+
]}
|
|
355
|
+
placeholder="Username"
|
|
356
|
+
placeholderTextColor={t.textDim}
|
|
357
|
+
value={username}
|
|
358
|
+
onChangeText={setUsername}
|
|
359
|
+
autoCapitalize="none"
|
|
360
|
+
autoCorrect={false}
|
|
361
|
+
editable={!registering}
|
|
362
|
+
/>
|
|
363
|
+
{checking ? (
|
|
364
|
+
<Text style={[styles.availabilityHint, { color: t.textDim }]}>
|
|
365
|
+
Checking...
|
|
366
|
+
</Text>
|
|
367
|
+
) : availability ? (
|
|
368
|
+
<Text
|
|
369
|
+
style={[
|
|
370
|
+
styles.availabilityHint,
|
|
371
|
+
{
|
|
372
|
+
color: availability.available ? t.success : t.errorText,
|
|
373
|
+
},
|
|
374
|
+
]}
|
|
375
|
+
>
|
|
376
|
+
{availability.available
|
|
377
|
+
? 'Available!'
|
|
378
|
+
: availability.reason || 'Username taken'}
|
|
379
|
+
</Text>
|
|
380
|
+
) : username.trim().length > 0 && username.trim().length < 3 ? (
|
|
381
|
+
<Text style={[styles.availabilityHint, { color: t.textDim }]}>
|
|
382
|
+
At least 3 characters
|
|
383
|
+
</Text>
|
|
384
|
+
) : null}
|
|
385
|
+
</View>
|
|
386
|
+
|
|
387
|
+
<TextInput
|
|
388
|
+
style={[
|
|
389
|
+
styles.input,
|
|
390
|
+
{
|
|
391
|
+
backgroundColor: t.surface,
|
|
392
|
+
color: t.text,
|
|
393
|
+
borderColor: t.border,
|
|
394
|
+
},
|
|
395
|
+
]}
|
|
396
|
+
placeholder="Referral code (optional)"
|
|
397
|
+
placeholderTextColor={t.textDim}
|
|
398
|
+
value={referralCode}
|
|
399
|
+
onChangeText={setReferralCode}
|
|
400
|
+
autoCapitalize="none"
|
|
401
|
+
autoCorrect={false}
|
|
402
|
+
editable={!registering}
|
|
403
|
+
/>
|
|
404
|
+
|
|
405
|
+
<TouchableOpacity
|
|
406
|
+
style={[
|
|
407
|
+
styles.button,
|
|
408
|
+
{ backgroundColor: t.accent, opacity: canSubmit ? 1 : 0.5 },
|
|
409
|
+
]}
|
|
410
|
+
onPress={() => {
|
|
411
|
+
Keyboard.dismiss();
|
|
412
|
+
onRegister(username.trim(), referralCode.trim() || undefined);
|
|
413
|
+
}}
|
|
414
|
+
disabled={!canSubmit}
|
|
415
|
+
activeOpacity={0.8}
|
|
416
|
+
>
|
|
417
|
+
{registering ? (
|
|
418
|
+
<ActivityIndicator color="#FFFFFF" size="small" />
|
|
419
|
+
) : (
|
|
420
|
+
<Text style={styles.buttonText}>Create Account</Text>
|
|
421
|
+
)}
|
|
422
|
+
</TouchableOpacity>
|
|
423
|
+
</View>
|
|
424
|
+
</View>
|
|
425
|
+
</View>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ── Styles ──
|
|
430
|
+
|
|
431
|
+
const styles = StyleSheet.create({
|
|
432
|
+
container: {
|
|
433
|
+
flex: 1,
|
|
434
|
+
justifyContent: 'center',
|
|
435
|
+
},
|
|
436
|
+
centerContent: {
|
|
437
|
+
flex: 1,
|
|
438
|
+
justifyContent: 'center',
|
|
439
|
+
alignItems: 'center',
|
|
440
|
+
paddingHorizontal: 32,
|
|
441
|
+
gap: 48,
|
|
442
|
+
},
|
|
443
|
+
spreadContent: {
|
|
444
|
+
flex: 1,
|
|
445
|
+
justifyContent: 'space-between',
|
|
446
|
+
paddingHorizontal: 32,
|
|
447
|
+
paddingTop: 120,
|
|
448
|
+
paddingBottom: 80,
|
|
449
|
+
},
|
|
450
|
+
brandingSection: {
|
|
451
|
+
alignItems: 'center',
|
|
452
|
+
gap: 12,
|
|
453
|
+
},
|
|
454
|
+
logoCircle: {
|
|
455
|
+
width: 80,
|
|
456
|
+
height: 80,
|
|
457
|
+
borderRadius: 40,
|
|
458
|
+
justifyContent: 'center',
|
|
459
|
+
alignItems: 'center',
|
|
460
|
+
marginBottom: 8,
|
|
461
|
+
},
|
|
462
|
+
logoText: {
|
|
463
|
+
fontSize: 36,
|
|
464
|
+
fontWeight: '800',
|
|
465
|
+
color: '#FFFFFF',
|
|
466
|
+
},
|
|
467
|
+
appName: {
|
|
468
|
+
fontSize: 32,
|
|
469
|
+
fontWeight: '800',
|
|
470
|
+
},
|
|
471
|
+
subtitle: {
|
|
472
|
+
fontSize: 16,
|
|
473
|
+
textAlign: 'center',
|
|
474
|
+
lineHeight: 22,
|
|
475
|
+
},
|
|
476
|
+
loadingSection: {
|
|
477
|
+
alignItems: 'center',
|
|
478
|
+
gap: 16,
|
|
479
|
+
},
|
|
480
|
+
statusText: {
|
|
481
|
+
fontSize: 16,
|
|
482
|
+
textAlign: 'center',
|
|
483
|
+
},
|
|
484
|
+
actionSection: {
|
|
485
|
+
gap: 16,
|
|
486
|
+
},
|
|
487
|
+
errorBox: {
|
|
488
|
+
borderWidth: 1,
|
|
489
|
+
borderRadius: 12,
|
|
490
|
+
paddingHorizontal: 16,
|
|
491
|
+
paddingVertical: 12,
|
|
492
|
+
},
|
|
493
|
+
errorText: {
|
|
494
|
+
fontSize: 14,
|
|
495
|
+
textAlign: 'center',
|
|
496
|
+
},
|
|
497
|
+
input: {
|
|
498
|
+
height: 56,
|
|
499
|
+
borderRadius: 16,
|
|
500
|
+
borderWidth: 1,
|
|
501
|
+
paddingHorizontal: 16,
|
|
502
|
+
fontSize: 16,
|
|
503
|
+
},
|
|
504
|
+
availabilityHint: {
|
|
505
|
+
fontSize: 13,
|
|
506
|
+
marginTop: 6,
|
|
507
|
+
paddingLeft: 4,
|
|
508
|
+
},
|
|
509
|
+
button: {
|
|
510
|
+
height: 56,
|
|
511
|
+
borderRadius: 16,
|
|
512
|
+
justifyContent: 'center',
|
|
513
|
+
alignItems: 'center',
|
|
514
|
+
},
|
|
515
|
+
buttonText: {
|
|
516
|
+
color: '#FFFFFF',
|
|
517
|
+
fontSize: 18,
|
|
518
|
+
fontWeight: '700',
|
|
519
|
+
},
|
|
520
|
+
});
|
package/src/ui/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { AuthGate } from './AuthGate';
|
|
2
|
+
export type { AuthGateProps, RegistrationScreenProps } from './AuthGate';
|
|
1
3
|
export { ConnectWalletScreen } from './ConnectWalletScreen';
|
|
2
4
|
export type { ConnectWalletScreenProps } from './ConnectWalletScreen';
|
|
3
5
|
export { UserProfileCard } from './UserProfileCard';
|