@coinbase/create-cdp-app 0.0.36 β†’ 0.0.38

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.
Files changed (31) hide show
  1. package/README.md +22 -9
  2. package/dist/index.js +168 -46
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/template-nextjs/README.md +3 -2
  6. package/template-nextjs/env.example +2 -0
  7. package/template-nextjs/package.json +3 -1
  8. package/template-nextjs/public/sol.svg +13 -0
  9. package/template-nextjs/src/app/globals.css +6 -0
  10. package/template-nextjs/src/components/Providers.tsx +32 -14
  11. package/template-nextjs/src/components/SignedInScreen.tsx +63 -15
  12. package/template-nextjs/src/components/SignedInScreenWithOnramp.tsx +22 -3
  13. package/template-nextjs/src/components/SolanaTransaction.tsx +157 -0
  14. package/template-nextjs/src/components/UserBalance.tsx +5 -3
  15. package/template-react/README.md +2 -1
  16. package/template-react/env.example +3 -0
  17. package/template-react/package.json +3 -1
  18. package/template-react/public/sol.svg +13 -0
  19. package/template-react/src/Header.tsx +20 -8
  20. package/template-react/src/SignedInScreen.tsx +66 -13
  21. package/template-react/src/SolanaTransaction.tsx +158 -0
  22. package/template-react/src/UserBalance.tsx +29 -10
  23. package/template-react/src/config.ts +25 -11
  24. package/template-react/src/index.css +6 -0
  25. package/template-react/src/main.tsx +2 -2
  26. package/template-react-native/App.tsx +79 -62
  27. package/template-react-native/EOATransaction.tsx +35 -22
  28. package/template-react-native/SmartAccountTransaction.tsx +9 -9
  29. package/template-react-native/components/SignInForm.tsx +433 -0
  30. package/template-react-native/types.ts +0 -22
  31. package/template-react-native/components/SignInModal.tsx +0 -342
@@ -0,0 +1,433 @@
1
+ import React, { useRef, useEffect } from "react";
2
+ import {
3
+ View,
4
+ TouchableOpacity,
5
+ Text,
6
+ TextInput,
7
+ KeyboardAvoidingView,
8
+ Platform,
9
+ StyleSheet,
10
+ ScrollView,
11
+ Dimensions,
12
+ Keyboard,
13
+ } from "react-native";
14
+ import { useTheme } from "../theme/ThemeContext";
15
+ import { AuthMethod } from "../types";
16
+
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
+ }
32
+
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
+ }) => {
48
+ const { colors } = useTheme();
49
+ const scrollViewRef = useRef<ScrollView>(null);
50
+
51
+ const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
52
+ const isSmallScreen = screenHeight < 700;
53
+
54
+ // Auto-scroll to ensure OTP field and button are visible
55
+ useEffect(() => {
56
+ if (flowId && scrollViewRef.current) {
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
+ });
79
+
80
+ const keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", () => {
81
+ if (flowId && scrollViewRef.current) {
82
+ setTimeout(() => {
83
+ scrollViewRef.current?.scrollTo({
84
+ y: 150, // Scroll back down a bit when keyboard hides
85
+ animated: true,
86
+ });
87
+ }, 100);
88
+ }
89
+ });
90
+
91
+ return () => {
92
+ keyboardDidShowListener.remove();
93
+ keyboardDidHideListener.remove();
94
+ };
95
+ }, [flowId]);
96
+
97
+ const createStyles = () =>
98
+ StyleSheet.create({
99
+ keyboardContainer: {
100
+ flex: 1,
101
+ },
102
+ scrollContainer: {
103
+ flexGrow: 1,
104
+ justifyContent: "center",
105
+ paddingHorizontal: 20,
106
+ paddingVertical: isSmallScreen ? 20 : 40,
107
+ paddingBottom: flowId ? (isSmallScreen ? 150 : 180) : isSmallScreen ? 20 : 40,
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,
161
+ },
162
+ logoText: {
163
+ color: "#ffffff",
164
+ fontSize: isSmallScreen ? 28 : 32,
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,
184
+ borderRadius: 8,
185
+ borderWidth: 1,
186
+ 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
+ backgroundColor: colors.inputBackground,
213
+ minHeight: Platform.OS === "android" ? 48 : 44,
214
+ justifyContent: "center",
215
+ },
216
+ flagText: {
217
+ fontSize: 16,
218
+ marginRight: 4,
219
+ },
220
+ countryCode: {
221
+ fontSize: 16,
222
+ color: colors.text,
223
+ 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
+ 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
+ },
249
+ continueButtonText: {
250
+ color: "#ffffff",
251
+ fontSize: 16,
252
+ fontWeight: "600",
253
+ },
254
+ buttonDisabled: {
255
+ opacity: 0.6,
256
+ },
257
+ dividerContainer: {
258
+ flexDirection: "row",
259
+ alignItems: "center",
260
+ marginBottom: isSmallScreen ? 20 : 24,
261
+ marginTop: 8,
262
+ },
263
+ dividerLine: {
264
+ flex: 1,
265
+ height: 1,
266
+ backgroundColor: colors.border,
267
+ },
268
+ dividerText: {
269
+ color: colors.textSecondary,
270
+ fontSize: 14,
271
+ marginHorizontal: 16,
272
+ },
273
+ toggleButton: {
274
+ backgroundColor: colors.inputBackground,
275
+ borderRadius: 8,
276
+ borderWidth: 1,
277
+ borderColor: colors.border,
278
+ paddingVertical: 16,
279
+ flexDirection: "row",
280
+ alignItems: "center",
281
+ justifyContent: "center",
282
+ minHeight: 48,
283
+ },
284
+ toggleIcon: {
285
+ fontSize: 18,
286
+ marginRight: 8,
287
+ },
288
+ toggleButtonText: {
289
+ color: colors.text,
290
+ fontSize: 16,
291
+ fontWeight: "500",
292
+ },
293
+ });
294
+
295
+ const styles = createStyles();
296
+
297
+ return (
298
+ <KeyboardAvoidingView
299
+ behavior={Platform.OS === "ios" ? "padding" : "position"}
300
+ style={styles.keyboardContainer}
301
+ keyboardVerticalOffset={Platform.OS === "ios" ? 60 : -150}
302
+ >
303
+ <ScrollView
304
+ ref={scrollViewRef}
305
+ contentContainerStyle={styles.scrollContainer}
306
+ keyboardShouldPersistTaps="handled"
307
+ showsVerticalScrollIndicator={false}
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>
324
+
325
+ <View style={styles.form}>
326
+ {/* Email/Phone Input Section */}
327
+ <Text style={styles.inputLabel}>
328
+ {authMethod === "email" ? "Email address" : "Phone number"}
329
+ </Text>
330
+ {authMethod === "sms" ? (
331
+ <View style={styles.inputContainer}>
332
+ <View style={styles.flagContainer}>
333
+ <Text style={styles.flagText}>πŸ‡ΊπŸ‡Έ</Text>
334
+ <Text style={styles.countryCode}>+1</Text>
335
+ </View>
336
+ <TextInput
337
+ style={[styles.phoneInput, flowId && styles.inputDisabled]}
338
+ value={phoneNumber}
339
+ onChangeText={setPhoneNumber}
340
+ placeholder="(000) 000-0000"
341
+ keyboardType="phone-pad"
342
+ autoCapitalize="none"
343
+ autoCorrect={false}
344
+ editable={!isLoading && !flowId}
345
+ placeholderTextColor={colors.textSecondary}
346
+ returnKeyType="done"
347
+ onSubmitEditing={flowId ? undefined : onSignIn}
348
+ blurOnSubmit={true}
349
+ />
350
+ </View>
351
+ ) : (
352
+ <TextInput
353
+ style={[styles.input, flowId && styles.inputDisabled]}
354
+ value={email}
355
+ onChangeText={setEmail}
356
+ placeholder="name@example.com"
357
+ keyboardType="email-address"
358
+ autoCapitalize="none"
359
+ autoCorrect={false}
360
+ editable={!isLoading && !flowId}
361
+ placeholderTextColor={colors.textSecondary}
362
+ returnKeyType="done"
363
+ onSubmitEditing={flowId ? undefined : onSignIn}
364
+ blurOnSubmit={true}
365
+ />
366
+ )}
367
+
368
+ {/* OTP Input Section - Shows when flowId exists */}
369
+ {flowId && (
370
+ <>
371
+ <Text style={styles.inputLabel}>Verification code</Text>
372
+ <TextInput
373
+ style={styles.input}
374
+ value={otp}
375
+ onChangeText={setOtp}
376
+ placeholder="Enter 6-digit code"
377
+ keyboardType="number-pad"
378
+ maxLength={6}
379
+ editable={!isLoading}
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
+ )}
427
+ </View>
428
+ </View>
429
+ </View>
430
+ </ScrollView>
431
+ </KeyboardAvoidingView>
432
+ );
433
+ };
@@ -19,28 +19,6 @@ export interface ThemeContextType {
19
19
 
20
20
  export type AuthMethod = "email" | "sms";
21
21
 
22
- export interface SignInModalProps {
23
- visible: boolean;
24
- onClose: () => void;
25
- authMethod: AuthMethod;
26
- onAuthMethodToggle: () => void;
27
- email: string;
28
- setEmail: (email: string) => void;
29
- phoneNumber: string;
30
- setPhoneNumber: (phoneNumber: string) => void;
31
- otp: string;
32
- setOtp: (otp: string) => void;
33
- flowId: string;
34
- isLoading: boolean;
35
- onSignIn: () => void;
36
- onVerifyOTP: () => void;
37
- slideAnim: any;
38
- }
39
-
40
- export interface WelcomeScreenProps {
41
- onSignInPress: () => void;
42
- }
43
-
44
22
  export interface DarkModeToggleProps {
45
23
  style?: any;
46
24
  iconStyle?: any;