Skip to content

Commit 5fd5b69

Browse files
feat: Add Google Sign-In option
I've added the functionality for you to sign in or sign up using your Google account. - Added a 'Sign in with Google' button to the Login and Signup screens. - Implemented the Google authentication flow using Expo's `expo-auth-session`. - Added a new API endpoint for Google login in the backend. - Updated the AuthContext to handle the Google sign-in state. - Added environment variable handling for Google Client IDs.
1 parent 19ca52a commit 5fd5b69

File tree

9 files changed

+184
-3
lines changed

9 files changed

+184
-3
lines changed

.github/workflows/preview.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ jobs:
2929
run: npm install
3030
working-directory: ./frontend
3131

32+
- name: Create .env file
33+
working-directory: ./frontend
34+
run: |
35+
echo "EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID }}" >> .env
36+
echo "EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID }}" >> .env
37+
echo "EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID }}" >> .env
38+
echo "EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID }}" >> .env
39+
3240
- name: Create preview
3341
uses: expo/expo-github-action/preview@v8
3442
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ yarn-error.*
4949

5050
# local env files
5151
.env*.local
52+
frontend/.env
5253

5354
# typescript
5455
*.tsbuildinfo

frontend/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID=
2+
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=
3+
EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID=
4+
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=

frontend/api/auth.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export const signup = (name, email, password) => {
88
return apiClient.post("/auth/signup/email", { name, email, password });
99
};
1010

11+
export const loginWithGoogle = (id_token) => {
12+
return apiClient.post("/auth/login/google", { id_token });
13+
};
14+
1115
export const updateUser = (userData) => apiClient.patch("/users/me", userData);
1216

1317
export const refresh = (refresh_token) => {

frontend/context/AuthContext.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {
66
setAuthTokens,
77
setTokenUpdateListener,
88
} from "../api/client";
9+
import { useAuthRequest } from "expo-auth-session/providers/google";
10+
import * as WebBrowser from "expo-web-browser";
11+
12+
WebBrowser.maybeCompleteAuthSession();
913

1014
export const AuthContext = createContext();
1115

@@ -15,13 +19,51 @@ export const AuthProvider = ({ children }) => {
1519
const [refresh, setRefresh] = useState(null);
1620
const [isLoading, setIsLoading] = useState(true);
1721

22+
const [request, response, promptAsync] = useAuthRequest({
23+
expoClientId: process.env.EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID,
24+
iosClientId: process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID,
25+
androidClientId: process.env.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID,
26+
webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
27+
});
28+
29+
useEffect(() => {
30+
const handleGoogleSignIn = async () => {
31+
if (response?.type === "success") {
32+
const { id_token } = response.params;
33+
try {
34+
const res = await authApi.loginWithGoogle(id_token);
35+
const { access_token, refresh_token, user: userData } = res.data;
36+
setToken(access_token);
37+
setRefresh(refresh_token);
38+
await setAuthTokens({
39+
newAccessToken: access_token,
40+
newRefreshToken: refresh_token,
41+
});
42+
const normalizedUser = userData?._id
43+
? userData
44+
: userData?.id
45+
? { ...userData, _id: userData.id }
46+
: userData;
47+
setUser(normalizedUser);
48+
} catch (error) {
49+
console.error(
50+
"Google login failed:",
51+
error.response?.data?.detail || error.message
52+
);
53+
}
54+
}
55+
};
56+
57+
handleGoogleSignIn();
58+
}, [response]);
59+
1860
// Load token and user data from AsyncStorage on app start
1961
useEffect(() => {
2062
const loadStoredAuth = async () => {
2163
try {
2264
const storedToken = await AsyncStorage.getItem("auth_token");
2365
const storedRefresh = await AsyncStorage.getItem("refresh_token");
24-
const storedUser = await AsyncStorage.getItem("user_data");
66+
const storedUser = await AsyncStorage.getItem("user_data");
2567

2668
if (storedToken && storedUser) {
2769
setToken(storedToken);
@@ -146,6 +188,10 @@ export const AuthProvider = ({ children }) => {
146188
}
147189
};
148190

191+
const loginWithGoogle = async () => {
192+
await promptAsync();
193+
};
194+
149195
const logout = async () => {
150196
try {
151197
// Clear stored authentication data
@@ -182,6 +228,7 @@ export const AuthProvider = ({ children }) => {
182228
signup,
183229
logout,
184230
updateUserInContext,
231+
loginWithGoogle,
185232
}}
186233
>
187234
{children}

frontend/package-lock.json

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"@react-navigation/native-stack": "^7.3.23",
1717
"axios": "^1.11.0",
1818
"expo": "~53.0.20",
19+
"expo-auth-session": "^6.2.1",
20+
"expo-crypto": "^14.1.5",
1921
"expo-image-picker": "~16.0.2",
2022
"expo-status-bar": "~2.2.3",
2123
"react": "19.0.0",

frontend/screens/LoginScreen.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const LoginScreen = ({ navigation }) => {
88
const [email, setEmail] = useState('');
99
const [password, setPassword] = useState('');
1010
const [isLoading, setIsLoading] = useState(false);
11-
const { login } = useContext(AuthContext);
11+
const { login, loginWithGoogle } = useContext(AuthContext);
1212

1313
const handleLogin = async () => {
1414
if (!email || !password) {
@@ -23,6 +23,18 @@ const LoginScreen = ({ navigation }) => {
2323
}
2424
};
2525

26+
const handleGoogleSignIn = async () => {
27+
setIsLoading(true);
28+
try {
29+
await loginWithGoogle();
30+
} catch (error) {
31+
console.error('Google Sign-In error:', error);
32+
Alert.alert('Google Sign-In Failed', 'An error occurred during Google Sign-In. Please try again.');
33+
} finally {
34+
setIsLoading(false);
35+
}
36+
};
37+
2638
return (
2739
<View style={styles.container}>
2840
<Text style={styles.title}>Welcome Back!</Text>
@@ -53,6 +65,16 @@ const LoginScreen = ({ navigation }) => {
5365
>
5466
Login
5567
</Button>
68+
<Button
69+
mode="contained"
70+
onPress={handleGoogleSignIn}
71+
style={[styles.button, styles.googleButton]}
72+
labelStyle={styles.buttonLabel}
73+
icon="google"
74+
disabled={isLoading}
75+
>
76+
Sign in with Google
77+
</Button>
5678
<Button
5779
onPress={() => navigation.navigate('Signup')}
5880
style={styles.signupButton}
@@ -86,6 +108,9 @@ const styles = StyleSheet.create({
86108
backgroundColor: colors.primary,
87109
paddingVertical: spacing.sm,
88110
},
111+
googleButton: {
112+
backgroundColor: '#4285F4', // Google's brand color
113+
},
89114
buttonLabel: {
90115
...typography.body,
91116
color: colors.white,

frontend/screens/SignupScreen.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const SignupScreen = ({ navigation }) => {
1010
const [password, setPassword] = useState('');
1111
const [confirmPassword, setConfirmPassword] = useState('');
1212
const [isLoading, setIsLoading] = useState(false);
13-
const { signup } = useContext(AuthContext);
13+
const { signup, loginWithGoogle } = useContext(AuthContext);
1414

1515
const handleSignup = async () => {
1616
if (!name || !email || !password || !confirmPassword) {
@@ -35,6 +35,18 @@ const SignupScreen = ({ navigation }) => {
3535
}
3636
};
3737

38+
const handleGoogleSignIn = async () => {
39+
setIsLoading(true);
40+
try {
41+
await loginWithGoogle();
42+
} catch (error) {
43+
console.error('Google Sign-In error:', error);
44+
Alert.alert('Google Sign-In Failed', 'An error occurred during Google Sign-In. Please try again.');
45+
} finally {
46+
setIsLoading(false);
47+
}
48+
};
49+
3850
return (
3951
<View style={styles.container}>
4052
<Text style={styles.title}>Create Account</Text>
@@ -81,6 +93,16 @@ const SignupScreen = ({ navigation }) => {
8193
>
8294
Sign Up
8395
</Button>
96+
<Button
97+
mode="contained"
98+
onPress={handleGoogleSignIn}
99+
style={[styles.button, styles.googleButton]}
100+
labelStyle={styles.buttonLabel}
101+
icon="google"
102+
disabled={isLoading}
103+
>
104+
Sign up with Google
105+
</Button>
84106
<Button
85107
onPress={() => navigation.navigate('Login')}
86108
style={styles.loginButton}
@@ -115,6 +137,9 @@ const styles = StyleSheet.create({
115137
backgroundColor: colors.primary,
116138
paddingVertical: spacing.sm,
117139
},
140+
googleButton: {
141+
backgroundColor: '#4285F4', // Google's brand color
142+
},
118143
buttonLabel: {
119144
...typography.body,
120145
color: colors.white,

0 commit comments

Comments
 (0)