@coinbase/create-cdp-app 0.0.28 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/create-cdp-app",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -9,6 +9,8 @@
9
9
  --cdp-example-accent-hover-color: #016969;
10
10
  --cdp-example-accent-foreground-color: #ffffff;
11
11
  --cdp-example-bg-low-contrast-color: #eeeeee;
12
+ --cdp-example-cdp-page-bg-color: #ffffff;
13
+ --cdp-example-cdp-page-bg-alt-color: #eef0f3;
12
14
  --cdp-example-card-bg-color: #ffffff;
13
15
  --cdp-example-card-border-color: #dcdcdc;
14
16
  --cdp-example-card-max-width: 30rem;
@@ -40,6 +42,8 @@
40
42
  --cdp-example-accent-hover-color: #04c0c0;
41
43
  --cdp-example-accent-foreground-color: #0a0b0d;
42
44
  --cdp-example-bg-low-contrast-color: #32353d;
45
+ --cdp-example-cdp-page-bg-color: #0a0b0d;
46
+ --cdp-example-cdp-page-bg-alt-color: #333333;
43
47
  --cdp-example-card-bg-color: #141519;
44
48
  --cdp-example-card-border-color: #24262a;
45
49
  }
@@ -27,8 +27,8 @@ export default function FundWallet({ onSuccess }: { onSuccess: () => void }) {
27
27
  <FundModal
28
28
  country="US"
29
29
  subdivision="CA"
30
- cryptoCurrency="ETH"
31
- fiatCurrency="USD"
30
+ cryptoCurrency="eth"
31
+ fiatCurrency="usd"
32
32
  fetchBuyQuote={fetchBuyQuote}
33
33
  fetchBuyOptions={fetchBuyOptions}
34
34
  network="base"
@@ -1,8 +1,8 @@
1
1
  import { type Theme } from "@coinbase/cdp-react/theme";
2
2
 
3
3
  export const theme: Partial<Theme> = {
4
- "colors-bg-alternate": "var(--cdp-example-page-bg-alt-color)",
5
- "colors-bg-default": "var(--cdp-example-card-bg-color)",
4
+ "colors-bg-alternate": "var(--cdp-example-cdp-page-bg-alt-color)",
5
+ "colors-bg-default": "var(--cdp-example-cdp-page-bg-color)",
6
6
  "colors-bg-overlay": "var(--cdp-example-bg-overlay-color)",
7
7
  "colors-bg-skeleton": "var(--cdp-example-bg-skeleton-color)",
8
8
  "colors-bg-primary": "var(--cdp-example-accent-color)",
@@ -9,6 +9,8 @@
9
9
  --cdp-example-accent-hover-color: #016969;
10
10
  --cdp-example-accent-foreground-color: #ffffff;
11
11
  --cdp-example-bg-low-contrast-color: #eeeeee;
12
+ --cdp-example-cdp-page-bg-color: #ffffff;
13
+ --cdp-example-cdp-page-bg-alt-color: #eef0f3;
12
14
  --cdp-example-card-bg-color: #ffffff;
13
15
  --cdp-example-card-border-color: #dcdcdc;
14
16
  --cdp-example-card-max-width: 30rem;
@@ -40,6 +42,8 @@
40
42
  --cdp-example-accent-hover-color: #04c0c0;
41
43
  --cdp-example-accent-foreground-color: #0a0b0d;
42
44
  --cdp-example-bg-low-contrast-color: #32353d;
45
+ --cdp-example-cdp-page-bg-color: #0a0b0d;
46
+ --cdp-example-cdp-page-bg-alt-color: #333333;
43
47
  --cdp-example-card-bg-color: #141519;
44
48
  --cdp-example-card-border-color: #24262a;
45
49
  }
@@ -1,8 +1,8 @@
1
1
  import { type Theme } from "@coinbase/cdp-react/theme";
2
2
 
3
3
  export const theme: Partial<Theme> = {
4
- "colors-bg-alternate": "var(--cdp-example-page-bg-alt-color)",
5
- "colors-bg-default": "var(--cdp-example-card-bg-color)",
4
+ "colors-bg-alternate": "var(--cdp-example-cdp-page-bg-alt-color)",
5
+ "colors-bg-default": "var(--cdp-example-cdp-page-bg-color)",
6
6
  "colors-bg-overlay": "var(--cdp-example-bg-overlay-color)",
7
7
  "colors-bg-skeleton": "var(--cdp-example-bg-skeleton-color)",
8
8
  "colors-bg-primary": "var(--cdp-example-accent-color)",
@@ -4,8 +4,11 @@ import {
4
4
  useIsInitialized,
5
5
  useSignInWithEmail,
6
6
  useVerifyEmailOTP,
7
+ useSignInWithSms,
8
+ useVerifySmsOTP,
7
9
  useIsSignedIn,
8
10
  useSignOut,
11
+ useCurrentUser,
9
12
  Config,
10
13
  } from "@coinbase/cdp-hooks";
11
14
  import { StatusBar } from "expo-status-bar";
@@ -15,13 +18,20 @@ import {
15
18
  Text,
16
19
  View,
17
20
  TouchableOpacity,
18
- TextInput,
19
21
  Alert,
20
22
  ScrollView,
21
23
  SafeAreaView,
24
+ Animated,
25
+ Dimensions,
22
26
  } from "react-native";
23
27
 
24
28
  import Transaction from "./Transaction";
29
+ import { ThemeProvider, useTheme } from "./theme/ThemeContext";
30
+ import { WelcomeScreen } from "./components/WelcomeScreen";
31
+ import { SignInModal } from "./components/SignInModal";
32
+ import { DarkModeToggle } from "./components/DarkModeToggle";
33
+ import { WalletHeader } from "./components/WalletHeader";
34
+ import { AuthMethod } from "./types";
25
35
 
26
36
  const cdpConfig: Config = {
27
37
  projectId: process.env.EXPO_PUBLIC_CDP_PROJECT_ID,
@@ -31,15 +41,16 @@ const cdpConfig: Config = {
31
41
  };
32
42
 
33
43
  /**
34
- * A multi-step authentication component that handles email-based sign-in flow.
44
+ * A multi-step authentication component that handles email and SMS-based sign-in flows.
35
45
  *
36
- * The component manages three states:
37
- * 1. Initial state: Displays a welcome screen with a sign-in button
38
- * 2. Email input: Collects and validates the user's email address
39
- * 3. OTP verification: Validates the one-time password sent to the user's email
46
+ * The component manages authentication states:
47
+ * 1. Initial state: Displays a welcome screen with sign-in options
48
+ * 2. Input: Collects and validates the user's email address or phone number
49
+ * 3. OTP verification: Validates the one-time password sent to the user's email or SMS
40
50
  *
41
51
  * Features:
42
- * - Email validation using regex
52
+ * - Toggle between email and SMS authentication
53
+ * - Email and phone number validation
43
54
  * - 6-digit OTP validation
44
55
  * - Loading states during API calls
45
56
  * - Error handling for failed authentication attempts
@@ -52,46 +63,78 @@ function CDPApp() {
52
63
  const { isSignedIn } = useIsSignedIn();
53
64
  const { signInWithEmail } = useSignInWithEmail();
54
65
  const { verifyEmailOTP } = useVerifyEmailOTP();
66
+ const { signInWithSms } = useSignInWithSms();
67
+ const { verifySmsOTP } = useVerifySmsOTP();
55
68
  const { signOut } = useSignOut();
56
69
  const { config } = useConfig();
70
+ const { colors, isDarkMode } = useTheme();
71
+ const { currentUser } = useCurrentUser();
57
72
 
73
+ const [authMethod, setAuthMethod] = useState<AuthMethod>("email");
58
74
  const [email, setEmail] = useState("");
75
+ const [phoneNumber, setPhoneNumber] = useState("");
59
76
  const [otp, setOtp] = useState("");
60
77
  const [flowId, setFlowId] = useState("");
61
78
  const [isLoading, setIsLoading] = useState(false);
79
+ const [showSignInModal, setShowSignInModal] = useState(false);
80
+ const [slideAnim] = useState(new Animated.Value(Dimensions.get("window").height));
62
81
 
63
82
  const handleSignIn = async () => {
64
- if (!email) {
65
- Alert.alert("Error", "Please enter an email address");
66
- return;
67
- }
83
+ if (authMethod === "email") {
84
+ if (!email) {
85
+ Alert.alert("Error", "Please enter an email address.");
86
+ return;
87
+ }
68
88
 
69
- setIsLoading(true);
70
- try {
71
- const result = await signInWithEmail({ email });
72
- setFlowId(result.flowId);
73
- Alert.alert("Success", "OTP sent to your email!");
74
- } catch (error) {
75
- Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign in");
76
- } finally {
77
- setIsLoading(false);
89
+ setIsLoading(true);
90
+ try {
91
+ const result = await signInWithEmail({ email });
92
+ setFlowId(result.flowId);
93
+ } catch (error) {
94
+ Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign in.");
95
+ } finally {
96
+ setIsLoading(false);
97
+ }
98
+ } else {
99
+ if (!phoneNumber) {
100
+ Alert.alert("Error", "Please enter a phone number.");
101
+ return;
102
+ }
103
+
104
+ setIsLoading(true);
105
+ try {
106
+ // Format phone number with country code for SMS
107
+ const formattedPhoneNumber = phoneNumber.startsWith("+")
108
+ ? phoneNumber
109
+ : `+1${phoneNumber.replace(/\D/g, "")}`;
110
+ const result = await signInWithSms({ phoneNumber: formattedPhoneNumber });
111
+ setFlowId(result.flowId);
112
+ } catch (error) {
113
+ Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign in.");
114
+ } finally {
115
+ setIsLoading(false);
116
+ }
78
117
  }
79
118
  };
80
119
 
81
120
  const handleVerifyOTP = async () => {
82
121
  if (!otp || !flowId) {
83
- Alert.alert("Error", "Please enter the OTP");
122
+ Alert.alert("Error", "Please enter the OTP.");
84
123
  return;
85
124
  }
86
125
 
87
126
  setIsLoading(true);
88
127
  try {
89
- await verifyEmailOTP({ flowId, otp });
90
- Alert.alert("Success", "Successfully signed in!");
128
+ if (authMethod === "email") {
129
+ await verifyEmailOTP({ flowId, otp });
130
+ } else {
131
+ await verifySmsOTP({ flowId, otp });
132
+ }
91
133
  setOtp("");
92
134
  setFlowId("");
135
+ closeSignInModal();
93
136
  } catch (error) {
94
- Alert.alert("Error", error instanceof Error ? error.message : "Failed to verify OTP");
137
+ Alert.alert("Error", error instanceof Error ? error.message : "Failed to verify OTP.");
95
138
  } finally {
96
139
  setIsLoading(false);
97
140
  }
@@ -101,21 +144,111 @@ function CDPApp() {
101
144
  try {
102
145
  await signOut();
103
146
  setEmail("");
147
+ setPhoneNumber("");
104
148
  setOtp("");
105
149
  setFlowId("");
106
- Alert.alert("Success", "Signed out successfully");
107
150
  } catch (error) {
108
- Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign out");
151
+ Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign out.");
109
152
  }
110
153
  };
111
154
 
155
+ const handleAuthMethodToggle = () => {
156
+ setAuthMethod(authMethod === "email" ? "sms" : "email");
157
+ setEmail("");
158
+ setPhoneNumber("");
159
+ setOtp("");
160
+ setFlowId("");
161
+ };
162
+
163
+ const openSignInModal = () => {
164
+ setShowSignInModal(true);
165
+ Animated.timing(slideAnim, {
166
+ toValue: 0,
167
+ duration: 300,
168
+ useNativeDriver: true,
169
+ }).start();
170
+ };
171
+
172
+ const closeSignInModal = () => {
173
+ Animated.timing(slideAnim, {
174
+ toValue: Dimensions.get("window").height,
175
+ duration: 300,
176
+ useNativeDriver: true,
177
+ }).start(() => {
178
+ setShowSignInModal(false);
179
+ setEmail("");
180
+ setPhoneNumber("");
181
+ setOtp("");
182
+ setFlowId("");
183
+ });
184
+ };
185
+
186
+ const createStyles = () =>
187
+ StyleSheet.create({
188
+ container: {
189
+ flex: 1,
190
+ backgroundColor: colors.background,
191
+ },
192
+ centerContent: {
193
+ flex: 1,
194
+ justifyContent: "center",
195
+ alignItems: "center",
196
+ padding: 20,
197
+ },
198
+ header: {
199
+ paddingHorizontal: 20,
200
+ paddingVertical: 16,
201
+ backgroundColor: colors.cardBackground,
202
+ borderBottomWidth: 1,
203
+ borderBottomColor: colors.border,
204
+ },
205
+ headerContent: {
206
+ flexDirection: "row",
207
+ alignItems: "center",
208
+ justifyContent: "space-between",
209
+ },
210
+ headerText: {
211
+ flex: 1,
212
+ alignItems: "flex-start",
213
+ paddingLeft: 4,
214
+ },
215
+ title: {
216
+ fontSize: 24,
217
+ fontWeight: "bold",
218
+ textAlign: "left",
219
+ color: colors.text,
220
+ },
221
+ text: {
222
+ fontSize: 16,
223
+ textAlign: "center",
224
+ color: colors.text,
225
+ },
226
+ content: {
227
+ flex: 1,
228
+ },
229
+ scrollView: {
230
+ flex: 1,
231
+ },
232
+ scrollContent: {
233
+ paddingVertical: 40,
234
+ paddingHorizontal: 20,
235
+ },
236
+ userContainer: {
237
+ width: "100%",
238
+ alignItems: "center",
239
+ marginBottom: 20,
240
+ },
241
+ });
242
+
243
+ const styles = createStyles();
244
+
112
245
  if (!isInitialized) {
113
246
  return (
114
247
  <SafeAreaView style={styles.container}>
115
248
  <View style={styles.centerContent}>
116
249
  <Text style={styles.text}>Initializing CDP...</Text>
117
250
  </View>
118
- <StatusBar style="auto" />
251
+ <StatusBar style={isDarkMode ? "light" : "dark"} />
119
252
  </SafeAreaView>
120
253
  );
121
254
  }
@@ -123,78 +256,52 @@ function CDPApp() {
123
256
  return (
124
257
  <SafeAreaView style={styles.container}>
125
258
  <View style={styles.header}>
126
- <Text style={styles.title}>CDP React Native Demo</Text>
127
- <Text style={styles.subtitle}>Project ID: {config.projectId}</Text>
259
+ <View style={styles.headerContent}>
260
+ <View style={styles.headerText}>
261
+ <Text style={styles.title}>CDP React Native Demo</Text>
262
+ </View>
263
+ <DarkModeToggle style={{ width: 40, height: 40 }} />
264
+ </View>
128
265
  </View>
129
266
 
130
267
  <View style={styles.content}>
131
268
  {!isSignedIn ? (
132
- <View style={styles.authContainer}>
133
- <Text style={styles.label}>Email:</Text>
134
- <TextInput
135
- style={styles.input}
136
- value={email}
137
- onChangeText={setEmail}
138
- placeholder="Enter your email"
139
- keyboardType="email-address"
140
- autoCapitalize="none"
141
- autoCorrect={false}
142
- editable={!isLoading}
143
- />
144
-
145
- <TouchableOpacity
146
- style={[styles.button, isLoading && styles.buttonDisabled]}
147
- onPress={handleSignIn}
148
- disabled={isLoading}
149
- >
150
- <Text style={styles.buttonText}>
151
- {isLoading ? "Sending..." : "Sign In with Email"}
152
- </Text>
153
- </TouchableOpacity>
154
-
155
- {flowId && (
156
- <>
157
- <Text style={styles.label}>Enter OTP from email:</Text>
158
- <TextInput
159
- style={styles.input}
160
- value={otp}
161
- onChangeText={setOtp}
162
- placeholder="Enter 6-digit OTP"
163
- keyboardType="number-pad"
164
- maxLength={6}
165
- editable={!isLoading}
166
- />
167
-
168
- <TouchableOpacity
169
- style={[styles.button, isLoading && styles.buttonDisabled]}
170
- onPress={handleVerifyOTP}
171
- disabled={isLoading}
172
- >
173
- <Text style={styles.buttonText}>{isLoading ? "Verifying..." : "Verify OTP"}</Text>
174
- </TouchableOpacity>
175
- </>
176
- )}
177
- </View>
269
+ <WelcomeScreen onSignInPress={openSignInModal} />
178
270
  ) : (
179
- <ScrollView
180
- style={styles.scrollView}
181
- showsVerticalScrollIndicator={false}
182
- contentContainerStyle={styles.scrollContent}
183
- >
184
- <View style={styles.userContainer}>
185
- <Text style={styles.welcomeText}>Welcome!</Text>
186
-
187
- <Transaction onSuccess={() => console.log("Transaction successful!")} />
188
-
189
- <TouchableOpacity style={styles.signOutButton} onPress={handleSignOut}>
190
- <Text style={styles.buttonText}>Sign Out</Text>
191
- </TouchableOpacity>
192
- </View>
193
- </ScrollView>
271
+ <>
272
+ <WalletHeader onSignOut={handleSignOut} />
273
+ <ScrollView
274
+ style={styles.scrollView}
275
+ showsVerticalScrollIndicator={false}
276
+ contentContainerStyle={styles.scrollContent}
277
+ >
278
+ <View style={styles.userContainer}>
279
+ <Transaction onSuccess={() => console.log("Transaction successful!")} />
280
+ </View>
281
+ </ScrollView>
282
+ </>
194
283
  )}
195
284
  </View>
196
285
 
197
- <StatusBar style="auto" />
286
+ <SignInModal
287
+ visible={showSignInModal}
288
+ onClose={closeSignInModal}
289
+ authMethod={authMethod}
290
+ onAuthMethodToggle={handleAuthMethodToggle}
291
+ email={email}
292
+ setEmail={setEmail}
293
+ phoneNumber={phoneNumber}
294
+ setPhoneNumber={setPhoneNumber}
295
+ otp={otp}
296
+ setOtp={setOtp}
297
+ flowId={flowId}
298
+ isLoading={isLoading}
299
+ onSignIn={handleSignIn}
300
+ onVerifyOTP={handleVerifyOTP}
301
+ slideAnim={slideAnim}
302
+ />
303
+
304
+ <StatusBar style={isDarkMode ? "light" : "dark"} />
198
305
  </SafeAreaView>
199
306
  );
200
307
  }
@@ -207,153 +314,9 @@ function CDPApp() {
207
314
  export default function App() {
208
315
  return (
209
316
  <CDPHooksProvider config={cdpConfig}>
210
- <CDPApp />
317
+ <ThemeProvider>
318
+ <CDPApp />
319
+ </ThemeProvider>
211
320
  </CDPHooksProvider>
212
321
  );
213
322
  }
214
-
215
- const styles = StyleSheet.create({
216
- container: {
217
- flex: 1,
218
- backgroundColor: "#eaeaea",
219
- },
220
- centerContent: {
221
- flex: 1,
222
- justifyContent: "center",
223
- alignItems: "center",
224
- padding: 20,
225
- },
226
- header: {
227
- paddingHorizontal: 20,
228
- paddingVertical: 16,
229
- backgroundColor: "#ffffff",
230
- alignItems: "center",
231
- borderBottomWidth: 1,
232
- borderBottomColor: "#dcdcdc",
233
- },
234
- content: {
235
- flex: 1,
236
- paddingHorizontal: 20,
237
- justifyContent: "center",
238
- },
239
- scrollView: {
240
- flex: 1,
241
- },
242
- scrollContent: {
243
- paddingVertical: 20,
244
- },
245
- title: {
246
- fontSize: 24,
247
- fontWeight: "bold",
248
- marginBottom: 10,
249
- textAlign: "center",
250
- color: "#111111",
251
- },
252
- subtitle: {
253
- fontSize: 14,
254
- color: "#757575",
255
- marginBottom: 30,
256
- textAlign: "center",
257
- },
258
- text: {
259
- fontSize: 16,
260
- textAlign: "center",
261
- color: "#111111",
262
- },
263
- authContainer: {
264
- width: "100%",
265
- maxWidth: 300,
266
- alignSelf: "center",
267
- },
268
- userContainer: {
269
- width: "100%",
270
- alignItems: "center",
271
- marginBottom: 20,
272
- },
273
- copyableInfoContainer: {
274
- width: "100%",
275
- marginBottom: 16,
276
- alignItems: "center",
277
- },
278
- infoLabel: {
279
- fontSize: 14,
280
- fontWeight: "600",
281
- color: "#757575",
282
- marginBottom: 6,
283
- textAlign: "center",
284
- },
285
- copyableInfo: {
286
- backgroundColor: "rgba(0, 128, 128, 0.1)",
287
- borderRadius: 8,
288
- padding: 12,
289
- borderWidth: 1,
290
- borderColor: "rgba(0, 128, 128, 0.3)",
291
- width: "100%",
292
- maxWidth: 280,
293
- },
294
- copyableText: {
295
- fontSize: 12,
296
- fontFamily: "monospace",
297
- color: "#008080",
298
- textAlign: "center",
299
- marginBottom: 4,
300
- },
301
- copyHint: {
302
- fontSize: 10,
303
- color: "#008080",
304
- fontStyle: "italic",
305
- textAlign: "center",
306
- },
307
- welcomeText: {
308
- fontSize: 20,
309
- fontWeight: "bold",
310
- marginBottom: 20,
311
- textAlign: "center",
312
- color: "#111111",
313
- },
314
- userInfo: {
315
- fontSize: 14,
316
- marginBottom: 8,
317
- textAlign: "center",
318
- color: "#111111",
319
- },
320
- label: {
321
- fontSize: 16,
322
- fontWeight: "500",
323
- marginBottom: 8,
324
- color: "#111111",
325
- },
326
- input: {
327
- borderWidth: 1,
328
- borderColor: "#dcdcdc",
329
- borderRadius: 8,
330
- padding: 12,
331
- fontSize: 16,
332
- marginBottom: 16,
333
- backgroundColor: "#ffffff",
334
- color: "#111111",
335
- },
336
- button: {
337
- backgroundColor: "#008080",
338
- padding: 15,
339
- borderRadius: 8,
340
- alignItems: "center",
341
- marginBottom: 12,
342
- },
343
- buttonDisabled: {
344
- opacity: 0.6,
345
- },
346
- buttonText: {
347
- color: "#ffffff",
348
- fontSize: 16,
349
- fontWeight: "600",
350
- },
351
- signOutButton: {
352
- backgroundColor: "#f44336",
353
- padding: 15,
354
- borderRadius: 8,
355
- alignItems: "center",
356
- marginTop: 20,
357
- width: "100%",
358
- },
359
- });