@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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { View, TouchableOpacity, Text, Alert } from "react-native";
|
|
3
|
+
import { useSignInWithOAuth } from "@coinbase/cdp-hooks";
|
|
4
|
+
import { useSignInFormStyles } from "../hooks/useSignInFormStyles";
|
|
5
|
+
|
|
6
|
+
export const OAuthForm: React.FC = () => {
|
|
7
|
+
const { signInWithOAuth } = useSignInWithOAuth();
|
|
8
|
+
const styles = useSignInFormStyles();
|
|
9
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
10
|
+
const [loadingProvider, setLoadingProvider] = useState<string | null>(null);
|
|
11
|
+
|
|
12
|
+
const handleOAuthSignIn = async (provider: "google" | "apple") => {
|
|
13
|
+
setIsLoading(true);
|
|
14
|
+
setLoadingProvider(provider);
|
|
15
|
+
try {
|
|
16
|
+
await signInWithOAuth(provider);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign in.");
|
|
19
|
+
} finally {
|
|
20
|
+
setIsLoading(false);
|
|
21
|
+
setLoadingProvider(null);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<View style={styles.header}>
|
|
28
|
+
<View style={styles.logoCircle}>
|
|
29
|
+
<Text style={styles.logoText}>C</Text>
|
|
30
|
+
</View>
|
|
31
|
+
<Text style={styles.title}>Sign in</Text>
|
|
32
|
+
</View>
|
|
33
|
+
<View style={styles.form}>
|
|
34
|
+
<TouchableOpacity
|
|
35
|
+
style={[styles.continueButton, isLoading && styles.buttonDisabled]}
|
|
36
|
+
onPress={() => handleOAuthSignIn("google")}
|
|
37
|
+
disabled={isLoading}
|
|
38
|
+
>
|
|
39
|
+
<Text style={styles.continueButtonText}>
|
|
40
|
+
{loadingProvider === "google" ? "Signing in..." : "Continue with Google"}
|
|
41
|
+
</Text>
|
|
42
|
+
</TouchableOpacity>
|
|
43
|
+
|
|
44
|
+
<TouchableOpacity
|
|
45
|
+
style={[styles.continueButton, isLoading && styles.buttonDisabled]}
|
|
46
|
+
onPress={() => handleOAuthSignIn("apple")}
|
|
47
|
+
disabled={isLoading}
|
|
48
|
+
>
|
|
49
|
+
<Text style={styles.continueButtonText}>
|
|
50
|
+
{loadingProvider === "apple" ? "Signing in..." : "Continue with Apple"}
|
|
51
|
+
</Text>
|
|
52
|
+
</TouchableOpacity>
|
|
53
|
+
</View>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
Text,
|
|
6
|
+
TextInput,
|
|
7
|
+
KeyboardAvoidingView,
|
|
8
|
+
Platform,
|
|
9
|
+
ScrollView,
|
|
10
|
+
Keyboard,
|
|
11
|
+
Alert,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { useVerifyEmailOTP, useVerifySmsOTP } from "@coinbase/cdp-hooks";
|
|
14
|
+
import { useTheme } from "../theme/ThemeContext";
|
|
15
|
+
import { useSignInFormStyles } from "../hooks/useSignInFormStyles";
|
|
16
|
+
import { AuthMethod } from "../types";
|
|
17
|
+
|
|
18
|
+
export interface OtpFormProps {
|
|
19
|
+
authMethod: AuthMethod;
|
|
20
|
+
flowId: string;
|
|
21
|
+
onSuccess: () => void;
|
|
22
|
+
onBack: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const OtpForm: React.FC<OtpFormProps> = ({ authMethod, flowId, onSuccess, onBack }) => {
|
|
26
|
+
const { colors } = useTheme();
|
|
27
|
+
const { verifyEmailOTP } = useVerifyEmailOTP();
|
|
28
|
+
const { verifySmsOTP } = useVerifySmsOTP();
|
|
29
|
+
const styles = useSignInFormStyles();
|
|
30
|
+
const [otp, setOtp] = useState("");
|
|
31
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
32
|
+
const scrollViewRef = useRef<ScrollView>(null);
|
|
33
|
+
|
|
34
|
+
// Auto-scroll to ensure OTP field and button are visible
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (scrollViewRef.current) {
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
scrollViewRef.current?.scrollTo({
|
|
39
|
+
y: 200,
|
|
40
|
+
animated: true,
|
|
41
|
+
});
|
|
42
|
+
}, 300);
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
// Handle keyboard events for better positioning
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", () => {
|
|
49
|
+
if (scrollViewRef.current) {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
scrollViewRef.current?.scrollTo({
|
|
52
|
+
y: 250,
|
|
53
|
+
animated: true,
|
|
54
|
+
});
|
|
55
|
+
}, 100);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", () => {
|
|
60
|
+
if (scrollViewRef.current) {
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
scrollViewRef.current?.scrollTo({
|
|
63
|
+
y: 150,
|
|
64
|
+
animated: true,
|
|
65
|
+
});
|
|
66
|
+
}, 100);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
keyboardDidShowListener.remove();
|
|
72
|
+
keyboardDidHideListener.remove();
|
|
73
|
+
};
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
const handleVerifyOTP = async () => {
|
|
77
|
+
if (!otp || !flowId) {
|
|
78
|
+
Alert.alert("Error", "Please enter the OTP.");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setIsLoading(true);
|
|
83
|
+
try {
|
|
84
|
+
if (authMethod === "email") {
|
|
85
|
+
await verifyEmailOTP({ flowId, otp });
|
|
86
|
+
} else {
|
|
87
|
+
await verifySmsOTP({ flowId, otp });
|
|
88
|
+
}
|
|
89
|
+
setOtp("");
|
|
90
|
+
onSuccess();
|
|
91
|
+
} catch (error) {
|
|
92
|
+
Alert.alert("Error", error instanceof Error ? error.message : "Failed to verify OTP.");
|
|
93
|
+
} finally {
|
|
94
|
+
setIsLoading(false);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<KeyboardAvoidingView
|
|
100
|
+
behavior={Platform.OS === "ios" ? "padding" : "position"}
|
|
101
|
+
style={styles.keyboardContainer}
|
|
102
|
+
keyboardVerticalOffset={Platform.OS === "ios" ? 60 : -150}
|
|
103
|
+
>
|
|
104
|
+
<ScrollView
|
|
105
|
+
ref={scrollViewRef}
|
|
106
|
+
contentContainerStyle={styles.scrollContainerWithOtp}
|
|
107
|
+
keyboardShouldPersistTaps="handled"
|
|
108
|
+
showsVerticalScrollIndicator={false}
|
|
109
|
+
>
|
|
110
|
+
<View style={styles.container}>
|
|
111
|
+
<View style={styles.card}>
|
|
112
|
+
<View style={styles.header}>
|
|
113
|
+
<TouchableOpacity style={styles.backButton} onPress={onBack} disabled={isLoading}>
|
|
114
|
+
<Text style={styles.backButtonText}>←</Text>
|
|
115
|
+
</TouchableOpacity>
|
|
116
|
+
<View style={styles.logoCircle}>
|
|
117
|
+
<Text style={styles.logoText}>C</Text>
|
|
118
|
+
</View>
|
|
119
|
+
<Text style={styles.title}>
|
|
120
|
+
Check your {authMethod === "email" ? "email" : "phone"}
|
|
121
|
+
</Text>
|
|
122
|
+
</View>
|
|
123
|
+
<View style={styles.form}>
|
|
124
|
+
<Text style={styles.inputLabel}>Verification code</Text>
|
|
125
|
+
<TextInput
|
|
126
|
+
style={styles.input}
|
|
127
|
+
value={otp}
|
|
128
|
+
onChangeText={setOtp}
|
|
129
|
+
placeholder="Enter 6-digit code"
|
|
130
|
+
keyboardType="number-pad"
|
|
131
|
+
maxLength={6}
|
|
132
|
+
editable={!isLoading}
|
|
133
|
+
placeholderTextColor={colors.textSecondary}
|
|
134
|
+
returnKeyType="done"
|
|
135
|
+
onSubmitEditing={handleVerifyOTP}
|
|
136
|
+
blurOnSubmit={true}
|
|
137
|
+
autoFocus={true}
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
<TouchableOpacity
|
|
141
|
+
style={[styles.continueButton, isLoading && styles.buttonDisabled]}
|
|
142
|
+
onPress={handleVerifyOTP}
|
|
143
|
+
disabled={isLoading}
|
|
144
|
+
>
|
|
145
|
+
<Text style={styles.continueButtonText}>
|
|
146
|
+
{isLoading ? "Verifying..." : "Verify Code"}
|
|
147
|
+
</Text>
|
|
148
|
+
</TouchableOpacity>
|
|
149
|
+
</View>
|
|
150
|
+
</View>
|
|
151
|
+
</View>
|
|
152
|
+
</ScrollView>
|
|
153
|
+
</KeyboardAvoidingView>
|
|
154
|
+
);
|
|
155
|
+
};
|