@coinbase/create-cdp-app 0.0.54 β 0.0.56
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.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template-react-native/App.tsx +2 -107
- package/template-react-native/app.json +3 -1
- package/template-react-native/components/EmailForm.tsx +70 -0
- package/template-react-native/components/OAuthForm.tsx +56 -0
- package/template-react-native/components/OtpForm.tsx +155 -0
- package/template-react-native/components/SignInForm.tsx +140 -352
- package/template-react-native/components/SmsForm.tsx +80 -0
- package/template-react-native/env.example +1 -0
- package/template-react-native/hooks/useSignInFormStyles.ts +172 -0
- package/template-react-native/package.json +1 -0
- package/template-react-native/types.ts +1 -1
|
@@ -1,258 +1,77 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
TouchableOpacity,
|
|
5
5
|
Text,
|
|
6
|
-
TextInput,
|
|
7
|
-
KeyboardAvoidingView,
|
|
8
|
-
Platform,
|
|
9
6
|
StyleSheet,
|
|
10
|
-
ScrollView,
|
|
11
7
|
Dimensions,
|
|
12
|
-
|
|
8
|
+
Platform,
|
|
9
|
+
ScrollView,
|
|
10
|
+
KeyboardAvoidingView,
|
|
13
11
|
} from "react-native";
|
|
14
|
-
import { useTheme } from "../theme/ThemeContext";
|
|
15
12
|
import { AuthMethod } from "../types";
|
|
13
|
+
import { useTheme } from "../theme/ThemeContext";
|
|
14
|
+
import { EmailForm } from "./EmailForm";
|
|
15
|
+
import { SmsForm } from "./SmsForm";
|
|
16
|
+
import { OAuthForm } from "./OAuthForm";
|
|
17
|
+
import { OtpForm } from "./OtpForm";
|
|
16
18
|
|
|
17
|
-
export interface SignInFormProps {
|
|
18
|
-
authMethod: AuthMethod;
|
|
19
|
-
onAuthMethodToggle: () => void;
|
|
20
|
-
email: string;
|
|
21
|
-
setEmail: (email: string) => void;
|
|
22
|
-
phoneNumber: string;
|
|
23
|
-
setPhoneNumber: (phoneNumber: string) => void;
|
|
24
|
-
otp: string;
|
|
25
|
-
setOtp: (otp: string) => void;
|
|
26
|
-
flowId: string;
|
|
27
|
-
isLoading: boolean;
|
|
28
|
-
onSignIn: () => void;
|
|
29
|
-
onVerifyOTP: () => void;
|
|
30
|
-
onBack?: () => void;
|
|
31
|
-
}
|
|
19
|
+
export interface SignInFormProps {}
|
|
32
20
|
|
|
33
|
-
export const SignInForm: React.FC<SignInFormProps> = ({
|
|
34
|
-
authMethod,
|
|
35
|
-
onAuthMethodToggle,
|
|
36
|
-
email,
|
|
37
|
-
setEmail,
|
|
38
|
-
phoneNumber,
|
|
39
|
-
setPhoneNumber,
|
|
40
|
-
otp,
|
|
41
|
-
setOtp,
|
|
42
|
-
flowId,
|
|
43
|
-
isLoading,
|
|
44
|
-
onSignIn,
|
|
45
|
-
onVerifyOTP,
|
|
46
|
-
onBack,
|
|
47
|
-
}) => {
|
|
21
|
+
export const SignInForm: React.FC<SignInFormProps> = () => {
|
|
48
22
|
const { colors } = useTheme();
|
|
49
|
-
const
|
|
23
|
+
const [authMethod, setAuthMethod] = useState<AuthMethod>("email");
|
|
24
|
+
const [flowId, setFlowId] = useState("");
|
|
50
25
|
|
|
51
26
|
const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
|
|
52
27
|
const isSmallScreen = screenHeight < 700;
|
|
53
28
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// Initial scroll when OTP field appears
|
|
58
|
-
setTimeout(() => {
|
|
59
|
-
scrollViewRef.current?.scrollTo({
|
|
60
|
-
y: 200, // Scroll up enough to show both OTP field and button
|
|
61
|
-
animated: true,
|
|
62
|
-
});
|
|
63
|
-
}, 300);
|
|
64
|
-
}
|
|
65
|
-
}, [flowId]);
|
|
66
|
-
|
|
67
|
-
// Handle keyboard events for better positioning
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
const keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", () => {
|
|
70
|
-
if (flowId && scrollViewRef.current) {
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
scrollViewRef.current?.scrollTo({
|
|
73
|
-
y: 250, // Scroll up more when keyboard is visible
|
|
74
|
-
animated: true,
|
|
75
|
-
});
|
|
76
|
-
}, 100);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
29
|
+
const handleSignInSuccess = (newFlowId: string) => {
|
|
30
|
+
setFlowId(newFlowId);
|
|
31
|
+
};
|
|
79
32
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
scrollViewRef.current?.scrollTo({
|
|
84
|
-
y: 150, // Scroll back down a bit when keyboard hides
|
|
85
|
-
animated: true,
|
|
86
|
-
});
|
|
87
|
-
}, 100);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
33
|
+
const handleVerifySuccess = () => {
|
|
34
|
+
setFlowId("");
|
|
35
|
+
};
|
|
90
36
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
-
}, [flowId]);
|
|
37
|
+
const handleBack = () => {
|
|
38
|
+
setFlowId("");
|
|
39
|
+
};
|
|
96
40
|
|
|
97
41
|
const createStyles = () =>
|
|
98
42
|
StyleSheet.create({
|
|
99
|
-
|
|
43
|
+
container: {
|
|
100
44
|
flex: 1,
|
|
101
45
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
justifyContent: "center",
|
|
46
|
+
tabContainer: {
|
|
47
|
+
flexDirection: "row",
|
|
105
48
|
paddingHorizontal: 20,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
minHeight: screenHeight - (Platform.OS === "ios" ? 100 : 80),
|
|
109
|
-
},
|
|
110
|
-
container: {
|
|
111
|
-
alignItems: "center",
|
|
112
|
-
justifyContent: "center",
|
|
113
|
-
},
|
|
114
|
-
card: {
|
|
115
|
-
backgroundColor: colors.cardBackground,
|
|
116
|
-
borderRadius: 16,
|
|
117
|
-
padding: isSmallScreen ? 24 : 32,
|
|
118
|
-
width: "100%",
|
|
119
|
-
maxWidth: 400,
|
|
120
|
-
minWidth: Math.min(screenWidth - 40, 280),
|
|
121
|
-
shadowColor: "#000",
|
|
122
|
-
shadowOffset: {
|
|
123
|
-
width: 0,
|
|
124
|
-
height: 2,
|
|
125
|
-
},
|
|
126
|
-
shadowOpacity: 0.1,
|
|
127
|
-
shadowRadius: 8,
|
|
128
|
-
elevation: 3,
|
|
129
|
-
},
|
|
130
|
-
header: {
|
|
131
|
-
alignItems: "center",
|
|
132
|
-
marginBottom: isSmallScreen ? 24 : 32,
|
|
133
|
-
position: "relative",
|
|
134
|
-
},
|
|
135
|
-
backButton: {
|
|
136
|
-
position: "absolute",
|
|
137
|
-
left: 0,
|
|
138
|
-
top: 0,
|
|
139
|
-
width: 40,
|
|
140
|
-
height: 40,
|
|
141
|
-
borderRadius: 20,
|
|
142
|
-
backgroundColor: colors.inputBackground,
|
|
143
|
-
borderWidth: 1,
|
|
144
|
-
borderColor: colors.border,
|
|
145
|
-
justifyContent: "center",
|
|
146
|
-
alignItems: "center",
|
|
147
|
-
},
|
|
148
|
-
backButtonText: {
|
|
149
|
-
fontSize: 18,
|
|
150
|
-
color: colors.text,
|
|
151
|
-
fontWeight: "600",
|
|
152
|
-
},
|
|
153
|
-
logoCircle: {
|
|
154
|
-
width: isSmallScreen ? 56 : 64,
|
|
155
|
-
height: isSmallScreen ? 56 : 64,
|
|
156
|
-
borderRadius: isSmallScreen ? 28 : 32,
|
|
157
|
-
backgroundColor: colors.accent,
|
|
158
|
-
justifyContent: "center",
|
|
159
|
-
alignItems: "center",
|
|
160
|
-
marginBottom: 16,
|
|
49
|
+
paddingTop: 20,
|
|
50
|
+
gap: 12,
|
|
161
51
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
fontWeight: "bold",
|
|
166
|
-
},
|
|
167
|
-
title: {
|
|
168
|
-
fontSize: isSmallScreen ? 20 : 24,
|
|
169
|
-
fontWeight: "500",
|
|
170
|
-
color: colors.text,
|
|
171
|
-
textAlign: "center",
|
|
172
|
-
},
|
|
173
|
-
form: {
|
|
174
|
-
width: "100%",
|
|
175
|
-
},
|
|
176
|
-
inputLabel: {
|
|
177
|
-
fontSize: 16,
|
|
178
|
-
fontWeight: "500",
|
|
179
|
-
color: colors.text,
|
|
180
|
-
marginBottom: 8,
|
|
181
|
-
},
|
|
182
|
-
input: {
|
|
183
|
-
backgroundColor: colors.inputBackground,
|
|
52
|
+
tab: {
|
|
53
|
+
flex: 1,
|
|
54
|
+
paddingVertical: 12,
|
|
184
55
|
borderRadius: 8,
|
|
185
56
|
borderWidth: 1,
|
|
186
57
|
borderColor: colors.border,
|
|
187
|
-
paddingHorizontal: 16,
|
|
188
|
-
paddingVertical: Platform.OS === "ios" ? 16 : 14,
|
|
189
|
-
fontSize: 16,
|
|
190
|
-
color: colors.text,
|
|
191
|
-
marginBottom: isSmallScreen ? 20 : 24,
|
|
192
|
-
minHeight: Platform.OS === "android" ? 48 : 44,
|
|
193
|
-
},
|
|
194
|
-
inputDisabled: {
|
|
195
|
-
opacity: 0.6,
|
|
196
|
-
backgroundColor: colors.border,
|
|
197
|
-
},
|
|
198
|
-
inputContainer: {
|
|
199
|
-
flexDirection: "row",
|
|
200
|
-
alignItems: "stretch",
|
|
201
|
-
marginBottom: isSmallScreen ? 20 : 24,
|
|
202
|
-
},
|
|
203
|
-
flagContainer: {
|
|
204
|
-
flexDirection: "row",
|
|
205
|
-
alignItems: "center",
|
|
206
|
-
paddingHorizontal: 12,
|
|
207
|
-
paddingVertical: Platform.OS === "ios" ? 16 : 14,
|
|
208
|
-
borderWidth: 1,
|
|
209
|
-
borderColor: colors.border,
|
|
210
|
-
borderTopLeftRadius: 8,
|
|
211
|
-
borderBottomLeftRadius: 8,
|
|
212
58
|
backgroundColor: colors.inputBackground,
|
|
213
|
-
|
|
214
|
-
justifyContent: "center",
|
|
59
|
+
alignItems: "center",
|
|
215
60
|
},
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
61
|
+
tabActive: {
|
|
62
|
+
backgroundColor: colors.accent,
|
|
63
|
+
borderColor: colors.accent,
|
|
219
64
|
},
|
|
220
|
-
|
|
221
|
-
fontSize:
|
|
222
|
-
color: colors.text,
|
|
65
|
+
tabText: {
|
|
66
|
+
fontSize: 14,
|
|
223
67
|
fontWeight: "500",
|
|
224
|
-
},
|
|
225
|
-
phoneInput: {
|
|
226
|
-
backgroundColor: colors.inputBackground,
|
|
227
|
-
borderRadius: 0,
|
|
228
|
-
borderTopRightRadius: 8,
|
|
229
|
-
borderBottomRightRadius: 8,
|
|
230
|
-
borderWidth: 1,
|
|
231
|
-
borderLeftWidth: 0,
|
|
232
|
-
borderColor: colors.border,
|
|
233
|
-
paddingHorizontal: 16,
|
|
234
|
-
paddingVertical: Platform.OS === "ios" ? 16 : 14,
|
|
235
|
-
fontSize: 16,
|
|
236
68
|
color: colors.text,
|
|
237
|
-
flex: 1,
|
|
238
|
-
minHeight: Platform.OS === "android" ? 48 : 44,
|
|
239
|
-
},
|
|
240
|
-
continueButton: {
|
|
241
|
-
backgroundColor: colors.accent,
|
|
242
|
-
borderRadius: 8,
|
|
243
|
-
paddingVertical: 16,
|
|
244
|
-
alignItems: "center",
|
|
245
|
-
marginBottom: isSmallScreen ? 20 : 24,
|
|
246
|
-
minHeight: 48,
|
|
247
|
-
justifyContent: "center",
|
|
248
69
|
},
|
|
249
|
-
|
|
70
|
+
tabTextActive: {
|
|
250
71
|
color: "#ffffff",
|
|
251
|
-
fontSize: 16,
|
|
252
|
-
fontWeight: "600",
|
|
253
72
|
},
|
|
254
|
-
|
|
255
|
-
|
|
73
|
+
formContainer: {
|
|
74
|
+
flex: 1,
|
|
256
75
|
},
|
|
257
76
|
dividerContainer: {
|
|
258
77
|
flexDirection: "row",
|
|
@@ -270,6 +89,9 @@ export const SignInForm: React.FC<SignInFormProps> = ({
|
|
|
270
89
|
fontSize: 14,
|
|
271
90
|
marginHorizontal: 16,
|
|
272
91
|
},
|
|
92
|
+
toggleButtonContainer: {
|
|
93
|
+
gap: 12,
|
|
94
|
+
},
|
|
273
95
|
toggleButton: {
|
|
274
96
|
backgroundColor: colors.inputBackground,
|
|
275
97
|
borderRadius: 8,
|
|
@@ -290,144 +112,110 @@ export const SignInForm: React.FC<SignInFormProps> = ({
|
|
|
290
112
|
fontSize: 16,
|
|
291
113
|
fontWeight: "500",
|
|
292
114
|
},
|
|
115
|
+
loginContainer: {
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
justifyContent: "center",
|
|
118
|
+
},
|
|
119
|
+
card: {
|
|
120
|
+
backgroundColor: colors.cardBackground,
|
|
121
|
+
borderRadius: 16,
|
|
122
|
+
padding: isSmallScreen ? 24 : 32,
|
|
123
|
+
width: "100%",
|
|
124
|
+
maxWidth: 400,
|
|
125
|
+
minWidth: Math.min(screenWidth - 40, 280),
|
|
126
|
+
minHeight: screenHeight - (Platform.OS === "ios" ? 350 : 300),
|
|
127
|
+
shadowColor: "#000",
|
|
128
|
+
shadowOffset: {
|
|
129
|
+
width: 0,
|
|
130
|
+
height: 2,
|
|
131
|
+
},
|
|
132
|
+
shadowOpacity: 0.1,
|
|
133
|
+
shadowRadius: 8,
|
|
134
|
+
elevation: 3,
|
|
135
|
+
},
|
|
136
|
+
keyboardContainer: {
|
|
137
|
+
flex: 1,
|
|
138
|
+
},
|
|
139
|
+
scrollContainer: {
|
|
140
|
+
flexGrow: 1,
|
|
141
|
+
justifyContent: "center",
|
|
142
|
+
paddingHorizontal: 20,
|
|
143
|
+
paddingVertical: isSmallScreen ? 20 : 40,
|
|
144
|
+
minHeight: screenHeight - (Platform.OS === "ios" ? 100 : 80),
|
|
145
|
+
},
|
|
293
146
|
});
|
|
294
147
|
|
|
295
148
|
const styles = createStyles();
|
|
296
149
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
>
|
|
309
|
-
<View style={styles.container}>
|
|
310
|
-
<View style={styles.card}>
|
|
311
|
-
<View style={styles.header}>
|
|
312
|
-
{flowId && onBack && (
|
|
313
|
-
<TouchableOpacity style={styles.backButton} onPress={onBack} disabled={isLoading}>
|
|
314
|
-
<Text style={styles.backButtonText}>β</Text>
|
|
315
|
-
</TouchableOpacity>
|
|
316
|
-
)}
|
|
317
|
-
<View style={styles.logoCircle}>
|
|
318
|
-
<Text style={styles.logoText}>C</Text>
|
|
319
|
-
</View>
|
|
320
|
-
<Text style={styles.title}>
|
|
321
|
-
{flowId ? "Check your " + (authMethod === "email" ? "email" : "phone") : "Sign in"}
|
|
322
|
-
</Text>
|
|
323
|
-
</View>
|
|
150
|
+
// If we have a flowId, show the OTP form (without tabs)
|
|
151
|
+
if (flowId) {
|
|
152
|
+
return (
|
|
153
|
+
<OtpForm
|
|
154
|
+
authMethod={authMethod}
|
|
155
|
+
flowId={flowId}
|
|
156
|
+
onSuccess={handleVerifySuccess}
|
|
157
|
+
onBack={handleBack}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
324
161
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
blurOnSubmit={true}
|
|
349
|
-
/>
|
|
162
|
+
// Otherwise show tabs and the selected form
|
|
163
|
+
return (
|
|
164
|
+
<View style={styles.container}>
|
|
165
|
+
<View style={styles.formContainer}>
|
|
166
|
+
<KeyboardAvoidingView
|
|
167
|
+
behavior={Platform.OS === "ios" ? "padding" : "position"}
|
|
168
|
+
style={styles.keyboardContainer}
|
|
169
|
+
keyboardVerticalOffset={Platform.OS === "ios" ? 60 : -150}
|
|
170
|
+
>
|
|
171
|
+
<ScrollView
|
|
172
|
+
contentContainerStyle={styles.scrollContainer}
|
|
173
|
+
keyboardShouldPersistTaps="handled"
|
|
174
|
+
showsVerticalScrollIndicator={false}
|
|
175
|
+
>
|
|
176
|
+
<View style={styles.loginContainer}>
|
|
177
|
+
<View style={styles.card}>
|
|
178
|
+
{authMethod === "email" && <EmailForm onSuccess={handleSignInSuccess} />}
|
|
179
|
+
{authMethod === "sms" && <SmsForm onSuccess={handleSignInSuccess} />}
|
|
180
|
+
{authMethod === "oauth" && <OAuthForm />}
|
|
181
|
+
<View style={styles.dividerContainer}>
|
|
182
|
+
<View style={styles.dividerLine} />
|
|
183
|
+
<Text style={styles.dividerText}>OR</Text>
|
|
184
|
+
<View style={styles.dividerLine} />
|
|
350
185
|
</View>
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
placeholderTextColor={colors.textSecondary}
|
|
381
|
-
returnKeyType="done"
|
|
382
|
-
onSubmitEditing={onVerifyOTP}
|
|
383
|
-
blurOnSubmit={true}
|
|
384
|
-
autoFocus={true}
|
|
385
|
-
/>
|
|
386
|
-
</>
|
|
387
|
-
)}
|
|
388
|
-
|
|
389
|
-
{/* Action Button */}
|
|
390
|
-
<TouchableOpacity
|
|
391
|
-
style={[styles.continueButton, isLoading && styles.buttonDisabled]}
|
|
392
|
-
onPress={flowId ? onVerifyOTP : onSignIn}
|
|
393
|
-
disabled={isLoading}
|
|
394
|
-
>
|
|
395
|
-
<Text style={styles.continueButtonText}>
|
|
396
|
-
{isLoading
|
|
397
|
-
? flowId
|
|
398
|
-
? "Verifying..."
|
|
399
|
-
: "Sending..."
|
|
400
|
-
: flowId
|
|
401
|
-
? "Verify Code"
|
|
402
|
-
: "Continue"}
|
|
403
|
-
</Text>
|
|
404
|
-
</TouchableOpacity>
|
|
405
|
-
|
|
406
|
-
{/* Auth Method Toggle - Only show when not in OTP mode */}
|
|
407
|
-
{!flowId && (
|
|
408
|
-
<>
|
|
409
|
-
<View style={styles.dividerContainer}>
|
|
410
|
-
<View style={styles.dividerLine} />
|
|
411
|
-
<Text style={styles.dividerText}>OR</Text>
|
|
412
|
-
<View style={styles.dividerLine} />
|
|
413
|
-
</View>
|
|
414
|
-
|
|
415
|
-
<TouchableOpacity
|
|
416
|
-
style={styles.toggleButton}
|
|
417
|
-
onPress={onAuthMethodToggle}
|
|
418
|
-
disabled={isLoading}
|
|
419
|
-
>
|
|
420
|
-
<Text style={styles.toggleIcon}>{authMethod === "email" ? "π" : "βοΈ"}</Text>
|
|
421
|
-
<Text style={styles.toggleButtonText}>
|
|
422
|
-
Continue with {authMethod === "email" ? "phone" : "email"}
|
|
423
|
-
</Text>
|
|
424
|
-
</TouchableOpacity>
|
|
425
|
-
</>
|
|
426
|
-
)}
|
|
186
|
+
<View style={styles.toggleButtonContainer}>
|
|
187
|
+
{authMethod !== "email" && (
|
|
188
|
+
<TouchableOpacity
|
|
189
|
+
style={styles.toggleButton}
|
|
190
|
+
onPress={() => setAuthMethod("email")}
|
|
191
|
+
>
|
|
192
|
+
<Text style={styles.toggleIcon}>{"βοΈ"}</Text>
|
|
193
|
+
<Text style={styles.toggleButtonText}>Continue with Email</Text>
|
|
194
|
+
</TouchableOpacity>
|
|
195
|
+
)}
|
|
196
|
+
{authMethod !== "sms" && (
|
|
197
|
+
<TouchableOpacity
|
|
198
|
+
style={styles.toggleButton}
|
|
199
|
+
onPress={() => setAuthMethod("sms")}
|
|
200
|
+
>
|
|
201
|
+
<Text style={styles.toggleIcon}>{"π"}</Text>
|
|
202
|
+
<Text style={styles.toggleButtonText}>Continue with SMS</Text>
|
|
203
|
+
</TouchableOpacity>
|
|
204
|
+
)}
|
|
205
|
+
{authMethod !== "oauth" && (
|
|
206
|
+
<TouchableOpacity
|
|
207
|
+
style={styles.toggleButton}
|
|
208
|
+
onPress={() => setAuthMethod("oauth")}
|
|
209
|
+
>
|
|
210
|
+
<Text style={styles.toggleButtonText}>Continue with Social</Text>
|
|
211
|
+
</TouchableOpacity>
|
|
212
|
+
)}
|
|
213
|
+
</View>
|
|
214
|
+
</View>
|
|
427
215
|
</View>
|
|
428
|
-
</
|
|
429
|
-
</
|
|
430
|
-
</
|
|
431
|
-
</
|
|
216
|
+
</ScrollView>
|
|
217
|
+
</KeyboardAvoidingView>
|
|
218
|
+
</View>
|
|
219
|
+
</View>
|
|
432
220
|
);
|
|
433
221
|
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { View, TouchableOpacity, Text, TextInput, Alert } from "react-native";
|
|
3
|
+
import { useSignInWithSms } from "@coinbase/cdp-hooks";
|
|
4
|
+
import { useTheme } from "../theme/ThemeContext";
|
|
5
|
+
import { useSignInFormStyles } from "../hooks/useSignInFormStyles";
|
|
6
|
+
|
|
7
|
+
export interface SmsFormProps {
|
|
8
|
+
onSuccess: (flowId: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const SmsForm: React.FC<SmsFormProps> = ({ onSuccess }) => {
|
|
12
|
+
const { colors } = useTheme();
|
|
13
|
+
const { signInWithSms } = useSignInWithSms();
|
|
14
|
+
const styles = useSignInFormStyles();
|
|
15
|
+
const [phoneNumber, setPhoneNumber] = useState("");
|
|
16
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
17
|
+
|
|
18
|
+
const handleSignIn = async () => {
|
|
19
|
+
if (!phoneNumber) {
|
|
20
|
+
Alert.alert("Error", "Please enter a phone number.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setIsLoading(true);
|
|
25
|
+
try {
|
|
26
|
+
// Format phone number with country code for SMS
|
|
27
|
+
const formattedPhoneNumber = phoneNumber.startsWith("+")
|
|
28
|
+
? phoneNumber
|
|
29
|
+
: `+1${phoneNumber.replace(/\D/g, "")}`;
|
|
30
|
+
const result = await signInWithSms({ phoneNumber: formattedPhoneNumber });
|
|
31
|
+
onSuccess(result.flowId);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign in.");
|
|
34
|
+
} finally {
|
|
35
|
+
setIsLoading(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<View style={styles.header}>
|
|
42
|
+
<View style={styles.logoCircle}>
|
|
43
|
+
<Text style={styles.logoText}>C</Text>
|
|
44
|
+
</View>
|
|
45
|
+
<Text style={styles.title}>Sign in</Text>
|
|
46
|
+
</View>
|
|
47
|
+
<View style={styles.form}>
|
|
48
|
+
<Text style={styles.inputLabel}>Phone number</Text>
|
|
49
|
+
<View style={styles.inputContainer}>
|
|
50
|
+
<View style={styles.flagContainer}>
|
|
51
|
+
<Text style={styles.flagText}>πΊπΈ</Text>
|
|
52
|
+
<Text style={styles.countryCode}>+1</Text>
|
|
53
|
+
</View>
|
|
54
|
+
<TextInput
|
|
55
|
+
style={styles.phoneInput}
|
|
56
|
+
value={phoneNumber}
|
|
57
|
+
onChangeText={setPhoneNumber}
|
|
58
|
+
placeholder="(000) 000-0000"
|
|
59
|
+
keyboardType="phone-pad"
|
|
60
|
+
autoCapitalize="none"
|
|
61
|
+
autoCorrect={false}
|
|
62
|
+
editable={!isLoading}
|
|
63
|
+
placeholderTextColor={colors.textSecondary}
|
|
64
|
+
returnKeyType="done"
|
|
65
|
+
onSubmitEditing={handleSignIn}
|
|
66
|
+
blurOnSubmit={true}
|
|
67
|
+
/>
|
|
68
|
+
</View>
|
|
69
|
+
|
|
70
|
+
<TouchableOpacity
|
|
71
|
+
style={[styles.continueButton, isLoading && styles.buttonDisabled]}
|
|
72
|
+
onPress={handleSignIn}
|
|
73
|
+
disabled={isLoading}
|
|
74
|
+
>
|
|
75
|
+
<Text style={styles.continueButtonText}>{isLoading ? "Sending..." : "Continue"}</Text>
|
|
76
|
+
</TouchableOpacity>
|
|
77
|
+
</View>
|
|
78
|
+
</>
|
|
79
|
+
);
|
|
80
|
+
};
|