diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db index 6eae76a..d9191b2 100644 Binary files a/apps/admin/data/fitai.db and b/apps/admin/data/fitai.db differ diff --git a/apps/mobile/src/app/(auth)/sign-in.tsx b/apps/mobile/src/app/(auth)/sign-in.tsx index ac9f428..afd96d7 100644 --- a/apps/mobile/src/app/(auth)/sign-in.tsx +++ b/apps/mobile/src/app/(auth)/sign-in.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import { useState, useEffect } from "react"; import { useSignIn, useAuth } from "@clerk/clerk-expo"; import { useRouter } from "expo-router"; import { @@ -12,19 +12,20 @@ import { Platform, ScrollView, } from "react-native"; +import { OAuthButtons } from "../../components/auth/OAuthButtons"; export default function SignInScreen() { const { signIn, setActive, isLoaded } = useSignIn(); const { isSignedIn } = useAuth(); const router = useRouter(); - const [emailAddress, setEmailAddress] = React.useState(""); - const [password, setPassword] = React.useState(""); - const [loading, setLoading] = React.useState(false); - const [error, setError] = React.useState(""); + const [emailAddress, setEmailAddress] = useState(""); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); // Redirect if already signed in - React.useEffect(() => { + useEffect(() => { if (isSignedIn) { router.replace("/(tabs)"); } @@ -64,7 +65,7 @@ export default function SignInScreen() { setError( err.errors?.[0]?.message || - "Failed to sign in. Please check your credentials.", + "Failed to sign in. Please check your credentials.", ); } finally { setLoading(false); @@ -100,6 +101,14 @@ export default function SignInScreen() { ) : null} + + + + + OR + + + Email @@ -256,4 +265,19 @@ const styles = StyleSheet.create({ color: "#2563eb", fontWeight: "600", }, + dividerContainer: { + flexDirection: "row", + alignItems: "center", + marginBottom: 24, + }, + dividerLine: { + flex: 1, + height: 1, + backgroundColor: "#ddd", + }, + dividerText: { + marginHorizontal: 10, + color: "#666", + fontSize: 14, + }, }); diff --git a/apps/mobile/src/app/(auth)/sign-up.tsx b/apps/mobile/src/app/(auth)/sign-up.tsx index 7ff1997..0326dc3 100644 --- a/apps/mobile/src/app/(auth)/sign-up.tsx +++ b/apps/mobile/src/app/(auth)/sign-up.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import { useState, useEffect } from "react"; import { useSignUp, useAuth } from "@clerk/clerk-expo"; import { useRouter } from "expo-router"; import { @@ -12,23 +12,24 @@ import { Platform, ScrollView, } from "react-native"; +import { OAuthButtons } from "../../components/auth/OAuthButtons"; export default function SignUpScreen() { const { signUp, setActive, isLoaded } = useSignUp(); const { isSignedIn } = useAuth(); const router = useRouter(); - const [emailAddress, setEmailAddress] = React.useState(""); - const [password, setPassword] = React.useState(""); - const [firstName, setFirstName] = React.useState(""); - const [lastName, setLastName] = React.useState(""); - const [pendingVerification, setPendingVerification] = React.useState(false); - const [code, setCode] = React.useState(""); - const [loading, setLoading] = React.useState(false); - const [error, setError] = React.useState(""); + const [emailAddress, setEmailAddress] = useState(""); + const [password, setPassword] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [pendingVerification, setPendingVerification] = useState(false); + const [code, setCode] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); // Redirect if already signed in - React.useEffect(() => { + useEffect(() => { if (isSignedIn) { router.replace("/(tabs)"); } @@ -182,6 +183,14 @@ export default function SignUpScreen() { ) : null} + + + + + OR + + + First Name @@ -372,4 +381,19 @@ const styles = StyleSheet.create({ color: "#2563eb", fontWeight: "600", }, + dividerContainer: { + flexDirection: "row", + alignItems: "center", + marginBottom: 24, + }, + dividerLine: { + flex: 1, + height: 1, + backgroundColor: "#ddd", + }, + dividerText: { + marginHorizontal: 10, + color: "#666", + fontSize: 14, + }, }); diff --git a/apps/mobile/src/components/auth/OAuthButtons.tsx b/apps/mobile/src/components/auth/OAuthButtons.tsx new file mode 100644 index 0000000..d0b08db --- /dev/null +++ b/apps/mobile/src/components/auth/OAuthButtons.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import * as WebBrowser from "expo-web-browser"; +import { Button, View, StyleSheet, TouchableOpacity, Text } from "react-native"; +import { useOAuth } from "@clerk/clerk-expo"; +import { useWarmUpBrowser } from "../../hooks/useWarmUpBrowser"; +import { Ionicons } from "@expo/vector-icons"; + +WebBrowser.maybeCompleteAuthSession(); + +export function OAuthButtons() { + useWarmUpBrowser(); + + const { startOAuthFlow } = useOAuth({ strategy: "oauth_google" }); + + const onPress = React.useCallback(async () => { + try { + const { createdSessionId, signIn, signUp, setActive } = + await startOAuthFlow(); + + if (createdSessionId) { + setActive!({ session: createdSessionId }); + } else { + // Use signIn or signUp for next steps such as MFA + } + } catch (err) { + console.error("OAuth error", err); + } + }, []); + + return ( + + + + Continue with Google + + + ); +} + +const styles = StyleSheet.create({ + container: { + width: "100%", + marginTop: 16, + }, + button: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + backgroundColor: "#fff", + borderWidth: 1, + borderColor: "#ddd", + paddingVertical: 12, + borderRadius: 8, + marginBottom: 12, + }, + icon: { + marginRight: 10, + }, + text: { + fontSize: 16, + fontWeight: "500", + color: "#333", + }, +}); diff --git a/apps/mobile/src/hooks/useWarmUpBrowser.ts b/apps/mobile/src/hooks/useWarmUpBrowser.ts new file mode 100644 index 0000000..9dd2212 --- /dev/null +++ b/apps/mobile/src/hooks/useWarmUpBrowser.ts @@ -0,0 +1,13 @@ +import React from "react"; +import * as WebBrowser from "expo-web-browser"; + +export const useWarmUpBrowser = () => { + React.useEffect(() => { + // Warm up the android browser to improve UX + // https://docs.expo.dev/guides/authentication/#improving-user-experience + void WebBrowser.warmUpAsync(); + return () => { + void WebBrowser.coolDownAsync(); + }; + }, []); +};