@coinbase/create-cdp-app 0.0.27 → 0.0.29

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.
@@ -0,0 +1,342 @@
1
+ import React from "react";
2
+ import {
3
+ Modal,
4
+ Animated,
5
+ View,
6
+ TouchableOpacity,
7
+ Text,
8
+ TextInput,
9
+ KeyboardAvoidingView,
10
+ Platform,
11
+ StyleSheet,
12
+ } from "react-native";
13
+ import { useTheme } from "../theme/ThemeContext";
14
+ import { SignInModalProps } from "../types";
15
+
16
+ export const SignInModal: React.FC<SignInModalProps> = ({
17
+ visible,
18
+ onClose,
19
+ authMethod,
20
+ onAuthMethodToggle,
21
+ email,
22
+ setEmail,
23
+ phoneNumber,
24
+ setPhoneNumber,
25
+ otp,
26
+ setOtp,
27
+ flowId,
28
+ isLoading,
29
+ onSignIn,
30
+ onVerifyOTP,
31
+ slideAnim,
32
+ }) => {
33
+ const { colors } = useTheme();
34
+
35
+ const createStyles = () =>
36
+ StyleSheet.create({
37
+ modalOverlay: {
38
+ flex: 1,
39
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
40
+ justifyContent: "flex-end",
41
+ },
42
+ modalContainer: {
43
+ backgroundColor: colors.cardBackground,
44
+ borderTopLeftRadius: 16,
45
+ borderTopRightRadius: 16,
46
+ minHeight: "60%",
47
+ maxHeight: "90%",
48
+ },
49
+ modalContent: {
50
+ flex: 1,
51
+ padding: 24,
52
+ },
53
+ modalCloseButton: {
54
+ position: "absolute",
55
+ top: 16,
56
+ right: 16,
57
+ zIndex: 1,
58
+ width: 32,
59
+ height: 32,
60
+ borderRadius: 16,
61
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
62
+ justifyContent: "center",
63
+ alignItems: "center",
64
+ },
65
+ modalCloseText: {
66
+ color: colors.text,
67
+ fontSize: 20,
68
+ fontWeight: "300",
69
+ textAlign: "center",
70
+ lineHeight: 20,
71
+ includeFontPadding: false,
72
+ },
73
+ modalHeader: {
74
+ alignItems: "center",
75
+ marginTop: 32,
76
+ marginBottom: 32,
77
+ },
78
+ logoCircle: {
79
+ width: 64,
80
+ height: 64,
81
+ borderRadius: 32,
82
+ backgroundColor: colors.accent,
83
+ justifyContent: "center",
84
+ alignItems: "center",
85
+ },
86
+ logoText: {
87
+ color: "#ffffff",
88
+ fontSize: 32,
89
+ fontWeight: "bold",
90
+ },
91
+ modalTitle: {
92
+ fontSize: 24,
93
+ fontWeight: "500",
94
+ color: colors.text,
95
+ textAlign: "center",
96
+ marginTop: 16,
97
+ },
98
+ modalForm: {
99
+ flex: 1,
100
+ },
101
+ inputLabel: {
102
+ fontSize: 16,
103
+ fontWeight: "500",
104
+ color: colors.text,
105
+ marginBottom: 8,
106
+ },
107
+ modalInput: {
108
+ backgroundColor: colors.inputBackground,
109
+ borderRadius: 8,
110
+ borderWidth: 1,
111
+ borderColor: colors.border,
112
+ paddingHorizontal: 16,
113
+ paddingVertical: 14,
114
+ fontSize: 16,
115
+ color: colors.text,
116
+ marginBottom: 24,
117
+ },
118
+ inputContainer: {
119
+ flexDirection: "row",
120
+ alignItems: "stretch",
121
+ marginBottom: 24,
122
+ },
123
+ flagContainer: {
124
+ flexDirection: "row",
125
+ alignItems: "center",
126
+ paddingHorizontal: 12,
127
+ paddingVertical: 14,
128
+ borderWidth: 1,
129
+ borderColor: colors.border,
130
+ borderTopLeftRadius: 8,
131
+ borderBottomLeftRadius: 8,
132
+ backgroundColor: colors.inputBackground,
133
+ },
134
+ flagText: {
135
+ fontSize: 16,
136
+ marginRight: 4,
137
+ },
138
+ countryCode: {
139
+ fontSize: 16,
140
+ color: colors.text,
141
+ fontWeight: "500",
142
+ },
143
+ phoneInput: {
144
+ backgroundColor: colors.inputBackground,
145
+ borderRadius: 0,
146
+ borderTopRightRadius: 8,
147
+ borderBottomRightRadius: 8,
148
+ borderWidth: 1,
149
+ borderLeftWidth: 0,
150
+ borderColor: colors.border,
151
+ paddingHorizontal: 16,
152
+ paddingVertical: 14,
153
+ fontSize: 16,
154
+ color: colors.text,
155
+ flex: 1,
156
+ },
157
+ continueButton: {
158
+ backgroundColor: colors.accent,
159
+ borderRadius: 8,
160
+ paddingVertical: 16,
161
+ alignItems: "center",
162
+ marginBottom: 24,
163
+ },
164
+ continueButtonText: {
165
+ color: "#ffffff",
166
+ fontSize: 16,
167
+ fontWeight: "600",
168
+ },
169
+ buttonDisabled: {
170
+ opacity: 0.6,
171
+ },
172
+ dividerContainer: {
173
+ flexDirection: "row",
174
+ alignItems: "center",
175
+ marginBottom: 24,
176
+ },
177
+ dividerLine: {
178
+ flex: 1,
179
+ height: 1,
180
+ backgroundColor: colors.border,
181
+ },
182
+ dividerText: {
183
+ color: colors.textSecondary,
184
+ fontSize: 14,
185
+ marginHorizontal: 16,
186
+ },
187
+ phoneButton: {
188
+ backgroundColor: colors.inputBackground,
189
+ borderRadius: 8,
190
+ borderWidth: 1,
191
+ borderColor: colors.border,
192
+ paddingVertical: 16,
193
+ flexDirection: "row",
194
+ alignItems: "center",
195
+ justifyContent: "center",
196
+ marginBottom: 32,
197
+ },
198
+ phoneIcon: {
199
+ fontSize: 18,
200
+ marginRight: 8,
201
+ },
202
+ phoneButtonText: {
203
+ color: colors.text,
204
+ fontSize: 16,
205
+ fontWeight: "500",
206
+ },
207
+ });
208
+
209
+ const styles = createStyles();
210
+
211
+ return (
212
+ <Modal visible={visible} transparent={true} animationType="none" onRequestClose={onClose}>
213
+ <View style={styles.modalOverlay}>
214
+ <Animated.View
215
+ style={[
216
+ styles.modalContainer,
217
+ {
218
+ transform: [{ translateY: slideAnim }],
219
+ },
220
+ ]}
221
+ >
222
+ <KeyboardAvoidingView
223
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
224
+ style={styles.modalContent}
225
+ >
226
+ <TouchableOpacity style={styles.modalCloseButton} onPress={onClose}>
227
+ <Text style={styles.modalCloseText}>×</Text>
228
+ </TouchableOpacity>
229
+
230
+ <View style={styles.modalHeader}>
231
+ <View style={styles.logoCircle}>
232
+ <Text style={styles.logoText}>C</Text>
233
+ </View>
234
+ <Text style={styles.modalTitle}>
235
+ {flowId ? "Enter verification code" : "Sign in"}
236
+ </Text>
237
+ </View>
238
+
239
+ {flowId ? (
240
+ <View style={styles.modalForm}>
241
+ <Text style={styles.inputLabel}>Verification code</Text>
242
+ <TextInput
243
+ style={styles.modalInput}
244
+ value={otp}
245
+ onChangeText={setOtp}
246
+ placeholder="Enter 6-digit code"
247
+ keyboardType="number-pad"
248
+ maxLength={6}
249
+ editable={!isLoading}
250
+ placeholderTextColor={colors.textSecondary}
251
+ returnKeyType="done"
252
+ onSubmitEditing={onVerifyOTP}
253
+ blurOnSubmit={true}
254
+ />
255
+
256
+ <TouchableOpacity
257
+ style={[styles.continueButton, isLoading && styles.buttonDisabled]}
258
+ onPress={onVerifyOTP}
259
+ disabled={isLoading}
260
+ >
261
+ <Text style={styles.continueButtonText}>
262
+ {isLoading ? "Verifying..." : "Continue"}
263
+ </Text>
264
+ </TouchableOpacity>
265
+ </View>
266
+ ) : (
267
+ <View style={styles.modalForm}>
268
+ <Text style={styles.inputLabel}>
269
+ {authMethod === "email" ? "Email address" : "Phone number"}
270
+ </Text>
271
+ {authMethod === "sms" ? (
272
+ <View style={styles.inputContainer}>
273
+ <View style={styles.flagContainer}>
274
+ <Text style={styles.flagText}>🇺🇸</Text>
275
+ <Text style={styles.countryCode}>+1</Text>
276
+ </View>
277
+ <TextInput
278
+ style={styles.phoneInput}
279
+ value={phoneNumber}
280
+ onChangeText={setPhoneNumber}
281
+ placeholder="(000) 000-0000"
282
+ keyboardType="phone-pad"
283
+ autoCapitalize="none"
284
+ autoCorrect={false}
285
+ editable={!isLoading}
286
+ placeholderTextColor={colors.textSecondary}
287
+ returnKeyType="done"
288
+ onSubmitEditing={onSignIn}
289
+ blurOnSubmit={true}
290
+ />
291
+ </View>
292
+ ) : (
293
+ <TextInput
294
+ style={styles.modalInput}
295
+ value={email}
296
+ onChangeText={setEmail}
297
+ placeholder="name@example.com"
298
+ keyboardType="email-address"
299
+ autoCapitalize="none"
300
+ autoCorrect={false}
301
+ editable={!isLoading}
302
+ placeholderTextColor={colors.textSecondary}
303
+ returnKeyType="done"
304
+ onSubmitEditing={onSignIn}
305
+ blurOnSubmit={true}
306
+ />
307
+ )}
308
+
309
+ <TouchableOpacity
310
+ style={[styles.continueButton, isLoading && styles.buttonDisabled]}
311
+ onPress={onSignIn}
312
+ disabled={isLoading}
313
+ >
314
+ <Text style={styles.continueButtonText}>
315
+ {isLoading ? "Sending..." : "Continue"}
316
+ </Text>
317
+ </TouchableOpacity>
318
+
319
+ <View style={styles.dividerContainer}>
320
+ <View style={styles.dividerLine} />
321
+ <Text style={styles.dividerText}>OR</Text>
322
+ <View style={styles.dividerLine} />
323
+ </View>
324
+
325
+ <TouchableOpacity
326
+ style={styles.phoneButton}
327
+ onPress={onAuthMethodToggle}
328
+ disabled={isLoading}
329
+ >
330
+ <Text style={styles.phoneIcon}>{authMethod === "email" ? "📞" : "✉️"}</Text>
331
+ <Text style={styles.phoneButtonText}>
332
+ Continue with {authMethod === "email" ? "phone" : "email"}
333
+ </Text>
334
+ </TouchableOpacity>
335
+ </View>
336
+ )}
337
+ </KeyboardAvoidingView>
338
+ </Animated.View>
339
+ </View>
340
+ </Modal>
341
+ );
342
+ };
@@ -0,0 +1,105 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+
4
+ interface SunMoonIconProps {
5
+ isDarkMode: boolean;
6
+ size?: number;
7
+ color?: string;
8
+ }
9
+
10
+ export const SunMoonIcon: React.FC<SunMoonIconProps> = ({
11
+ isDarkMode,
12
+ size = 16,
13
+ color = "#666666",
14
+ }) => {
15
+ const createStyles = () =>
16
+ StyleSheet.create({
17
+ container: {
18
+ width: size,
19
+ height: size,
20
+ alignItems: "center",
21
+ justifyContent: "center",
22
+ },
23
+ // Sun icon (circle with rays)
24
+ sunContainer: {
25
+ width: size,
26
+ height: size,
27
+ alignItems: "center",
28
+ justifyContent: "center",
29
+ position: "relative",
30
+ },
31
+ sunCore: {
32
+ width: size * 0.6,
33
+ height: size * 0.6,
34
+ borderRadius: (size * 0.6) / 2,
35
+ backgroundColor: color,
36
+ },
37
+ sunRay: {
38
+ position: "absolute",
39
+ width: 2,
40
+ height: size * 0.2,
41
+ backgroundColor: color,
42
+ },
43
+ sunRayTop: {
44
+ top: 0,
45
+ left: (size - 2) / 2,
46
+ },
47
+ sunRayRight: {
48
+ right: 0,
49
+ top: (size - size * 0.2) / 2,
50
+ transform: [{ rotate: "90deg" }],
51
+ },
52
+ sunRayBottom: {
53
+ bottom: 0,
54
+ left: (size - 2) / 2,
55
+ },
56
+ sunRayLeft: {
57
+ left: 0,
58
+ top: (size - size * 0.2) / 2,
59
+ transform: [{ rotate: "90deg" }],
60
+ },
61
+ // Moon icon (simple crescent shape)
62
+ moonContainer: {
63
+ width: size,
64
+ height: size,
65
+ alignItems: "center",
66
+ justifyContent: "center",
67
+ },
68
+ moonShape: {
69
+ width: size * 0.7,
70
+ height: size * 0.7,
71
+ borderRadius: (size * 0.7) / 2,
72
+ backgroundColor: "transparent",
73
+ borderWidth: 2,
74
+ borderColor: color,
75
+ borderRightColor: "transparent",
76
+ transform: [{ rotate: "20deg" }],
77
+ },
78
+ });
79
+
80
+ const styles = createStyles();
81
+
82
+ if (isDarkMode) {
83
+ // Show sun icon (switching to light mode)
84
+ return (
85
+ <View style={styles.container}>
86
+ <View style={styles.sunContainer}>
87
+ <View style={[styles.sunRay, styles.sunRayTop]} />
88
+ <View style={[styles.sunRay, styles.sunRayRight]} />
89
+ <View style={[styles.sunRay, styles.sunRayBottom]} />
90
+ <View style={[styles.sunRay, styles.sunRayLeft]} />
91
+ <View style={styles.sunCore} />
92
+ </View>
93
+ </View>
94
+ );
95
+ } else {
96
+ // Show moon icon (switching to dark mode)
97
+ return (
98
+ <View style={styles.container}>
99
+ <View style={styles.moonContainer}>
100
+ <View style={styles.moonShape} />
101
+ </View>
102
+ </View>
103
+ );
104
+ }
105
+ };
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+
4
+ interface UserIconProps {
5
+ size?: number;
6
+ color?: string;
7
+ }
8
+
9
+ export const UserIcon: React.FC<UserIconProps> = ({ size = 24, color = "#666666" }) => {
10
+ const createStyles = () =>
11
+ StyleSheet.create({
12
+ container: {
13
+ width: size,
14
+ height: size,
15
+ alignItems: "center",
16
+ justifyContent: "center",
17
+ },
18
+ circle: {
19
+ width: size,
20
+ height: size,
21
+ borderRadius: size / 2,
22
+ borderWidth: 1.5,
23
+ borderColor: color,
24
+ alignItems: "center",
25
+ justifyContent: "flex-start",
26
+ overflow: "hidden",
27
+ },
28
+ head: {
29
+ width: size * 0.3,
30
+ height: size * 0.3,
31
+ borderRadius: (size * 0.3) / 2,
32
+ backgroundColor: color,
33
+ marginTop: size * 0.15,
34
+ },
35
+ body: {
36
+ width: size * 0.55,
37
+ height: size * 0.35,
38
+ borderRadius: size * 0.275,
39
+ backgroundColor: color,
40
+ marginTop: size * 0.05,
41
+ },
42
+ });
43
+
44
+ const styles = createStyles();
45
+
46
+ return (
47
+ <View style={styles.container}>
48
+ <View style={styles.circle}>
49
+ <View style={styles.head} />
50
+ <View style={styles.body} />
51
+ </View>
52
+ </View>
53
+ );
54
+ };
@@ -0,0 +1,102 @@
1
+ import React from "react";
2
+ import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
3
+ import * as Clipboard from "expo-clipboard";
4
+ import { useEvmAddress, useCurrentUser } from "@coinbase/cdp-hooks";
5
+ import { useTheme } from "../theme/ThemeContext";
6
+ import { UserIcon } from "./UserIcon";
7
+
8
+ interface WalletHeaderProps {
9
+ onSignOut: () => void;
10
+ }
11
+
12
+ export const WalletHeader: React.FC<WalletHeaderProps> = ({ onSignOut }) => {
13
+ const { colors } = useTheme();
14
+ const { evmAddress } = useEvmAddress();
15
+ const { currentUser } = useCurrentUser();
16
+
17
+ // Use EVM address if available, otherwise fall back to smart account
18
+ const walletAddress = evmAddress || currentUser?.evmSmartAccounts?.[0];
19
+
20
+ const formatAddress = (address: string) => {
21
+ if (!address) return "";
22
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
23
+ };
24
+
25
+ const copyWalletAddress = async () => {
26
+ if (!walletAddress) return;
27
+
28
+ try {
29
+ await Clipboard.setStringAsync(walletAddress);
30
+ Alert.alert("Copied!", "Wallet address copied to clipboard.");
31
+ } catch (error) {
32
+ Alert.alert("Error", "Failed to copy wallet address.");
33
+ }
34
+ };
35
+
36
+ const createStyles = () =>
37
+ StyleSheet.create({
38
+ container: {
39
+ backgroundColor: colors.cardBackground,
40
+ paddingHorizontal: 20,
41
+ paddingVertical: 16,
42
+ borderBottomWidth: 1,
43
+ borderBottomColor: colors.border,
44
+ flexDirection: "row",
45
+ alignItems: "center",
46
+ justifyContent: "space-between",
47
+ },
48
+ leftContent: {
49
+ flexDirection: "row",
50
+ alignItems: "center",
51
+ flex: 1,
52
+ },
53
+ avatar: {
54
+ width: 32,
55
+ height: 32,
56
+ borderRadius: 16,
57
+ backgroundColor: colors.textSecondary,
58
+ marginRight: 12,
59
+ justifyContent: "center",
60
+ alignItems: "center",
61
+ },
62
+ addressContainer: {
63
+ flex: 1,
64
+ },
65
+ address: {
66
+ fontSize: 16,
67
+ fontWeight: "500",
68
+ color: colors.text,
69
+ },
70
+ signOutButton: {
71
+ backgroundColor: colors.accent,
72
+ paddingHorizontal: 16,
73
+ paddingVertical: 8,
74
+ borderRadius: 20,
75
+ },
76
+ signOutButtonText: {
77
+ color: "#ffffff",
78
+ fontSize: 14,
79
+ fontWeight: "600",
80
+ },
81
+ });
82
+
83
+ const styles = createStyles();
84
+
85
+ return (
86
+ <View style={styles.container}>
87
+ <TouchableOpacity style={styles.leftContent} onPress={copyWalletAddress}>
88
+ <View style={styles.avatar}>
89
+ <UserIcon size={18} color={colors.cardBackground} />
90
+ </View>
91
+ <View style={styles.addressContainer}>
92
+ <Text style={styles.address}>
93
+ {walletAddress ? formatAddress(walletAddress) : "Loading..."}
94
+ </Text>
95
+ </View>
96
+ </TouchableOpacity>
97
+ <TouchableOpacity style={styles.signOutButton} onPress={onSignOut}>
98
+ <Text style={styles.signOutButtonText}>Sign out</Text>
99
+ </TouchableOpacity>
100
+ </View>
101
+ );
102
+ };
@@ -0,0 +1,73 @@
1
+ import React from "react";
2
+ import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { useTheme } from "../theme/ThemeContext";
4
+ import { WelcomeScreenProps } from "../types";
5
+
6
+ export const WelcomeScreen: React.FC<WelcomeScreenProps> = ({ onSignInPress }) => {
7
+ const { colors } = useTheme();
8
+
9
+ const createStyles = () =>
10
+ StyleSheet.create({
11
+ container: {
12
+ flex: 1,
13
+ justifyContent: "center",
14
+ alignItems: "center",
15
+ padding: 20,
16
+ },
17
+ card: {
18
+ backgroundColor: colors.cardBackground,
19
+ borderRadius: 12,
20
+ padding: 32,
21
+ alignItems: "center",
22
+ width: "100%",
23
+ maxWidth: 400,
24
+ shadowColor: "#000",
25
+ shadowOffset: {
26
+ width: 0,
27
+ height: 2,
28
+ },
29
+ shadowOpacity: 0.1,
30
+ shadowRadius: 8,
31
+ elevation: 3,
32
+ },
33
+ title: {
34
+ fontSize: 28,
35
+ fontWeight: "bold",
36
+ color: colors.text,
37
+ marginBottom: 8,
38
+ textAlign: "center",
39
+ },
40
+ subtitle: {
41
+ fontSize: 16,
42
+ color: colors.textSecondary,
43
+ marginBottom: 24,
44
+ textAlign: "center",
45
+ },
46
+ signInButton: {
47
+ backgroundColor: colors.accent,
48
+ borderRadius: 12,
49
+ paddingVertical: 16,
50
+ paddingHorizontal: 48,
51
+ alignItems: "center",
52
+ },
53
+ signInButtonText: {
54
+ color: "#ffffff",
55
+ fontSize: 16,
56
+ fontWeight: "600",
57
+ },
58
+ });
59
+
60
+ const styles = createStyles();
61
+
62
+ return (
63
+ <View style={styles.container}>
64
+ <View style={styles.card}>
65
+ <Text style={styles.title}>Welcome!</Text>
66
+ <Text style={styles.subtitle}>Please sign in to continue.</Text>
67
+ <TouchableOpacity style={styles.signInButton} onPress={onSignInPress}>
68
+ <Text style={styles.signInButtonText}>Sign in</Text>
69
+ </TouchableOpacity>
70
+ </View>
71
+ </View>
72
+ );
73
+ };