diff --git a/README.md b/README.md
index 67da738..04669ae 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ CodeBuilder Admin is a mobile application built with React Native and Expo. This
```env
GOOGLE_MAPS_API_KEY=your_google_maps_api_key
+ GOOGLE_WEB_CLIENT_ID=your_google_web_client_id
FIREBASE_API_KEY=your_firebase_api_key
FIREBASE_AUTH_DOMAIN=your_firebase_auth_domain
FIREBASE_PROJECT_ID=your_firebase_project_id
diff --git a/app.config.js b/app.config.js
index 97e869a..eb3d6b2 100644
--- a/app.config.js
+++ b/app.config.js
@@ -42,6 +42,7 @@ module.exports = {
firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET,
firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
firebaseAppId: process.env.FIREBASE_APP_ID,
+ googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID,
},
orientation: "portrait",
icon: "./assets/images/icon.png",
@@ -135,6 +136,7 @@ module.exports = {
],
"@react-native-firebase/app",
"@react-native-firebase/messaging",
+ "@react-native-google-signin/google-signin",
// Add the iOS sound plugin
[withIOSSounds],
[
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 64d061e..fbac8d1 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useEffect } from "react";
import { useNavigationContainerRef } from "@react-navigation/native";
import { useReactNavigationDevTools } from "@dev-plugins/react-navigation";
import { SplashScreen } from "expo-router";
@@ -18,6 +18,7 @@ import "react-native-reanimated";
import { useColorScheme } from "@/hooks/useColorScheme";
import { registerBackgroundFetch } from "@/utils/tasks.utils";
import ErrorBoundary from "@/components/ErrorBoundary";
+import { AuthProvider, useAuth } from "@/hooks/useAuth";
import {
setJSExceptionHandler,
@@ -100,17 +101,26 @@ export default function RootLayout() {
return null;
}
- return ;
+ return (
+
+
+
+ );
}
function RootLayoutNav() {
const colorScheme = useColorScheme();
+ const { user } = useAuth();
return (
-
+ {user ? (
+
+ ) : (
+
+ )}
diff --git a/app/login.tsx b/app/login.tsx
new file mode 100644
index 0000000..6e89e19
--- /dev/null
+++ b/app/login.tsx
@@ -0,0 +1,46 @@
+import { useEffect } from 'react';
+import { View, StyleSheet } from 'react-native';
+import { GoogleSignin, GoogleSigninButton } from '@react-native-google-signin/google-signin';
+import Constants from 'expo-constants';
+import { useAuth } from '@/hooks/useAuth';
+
+export default function LoginScreen() {
+ const { setUser } = useAuth();
+
+ useEffect(() => {
+ GoogleSignin.configure({
+ webClientId: Constants.expoConfig?.extra?.googleWebClientId,
+ });
+ }, []);
+
+ const signIn = async () => {
+ try {
+ await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
+ const result = await GoogleSignin.signIn();
+ if (result.type === 'success') {
+ setUser({ idToken: result.data.idToken ?? '', user: result.data.user });
+ fetch('https://new.codebuilder.org/api/auth/google', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ idToken: result.data.idToken }),
+ }).catch((e) => console.error('Auth callback error:', e));
+ }
+ } catch (e) {
+ console.error('Google sign in error:', e);
+ }
+ };
+
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
+});
diff --git a/hooks/useAuth.ts b/hooks/useAuth.ts
new file mode 100644
index 0000000..a6bf930
--- /dev/null
+++ b/hooks/useAuth.ts
@@ -0,0 +1,34 @@
+import { createContext, useContext, useState, ReactNode } from 'react';
+
+export interface AuthUser {
+ idToken: string;
+ user: {
+ id: string;
+ name: string | null;
+ email: string;
+ photo: string | null;
+ familyName: string | null;
+ givenName: string | null;
+ };
+}
+
+interface AuthContextType {
+ user: AuthUser | null;
+ setUser: (user: AuthUser | null) => void;
+}
+
+const AuthContext = createContext({
+ user: null,
+ setUser: () => {},
+});
+
+export const AuthProvider = ({ children }: { children: ReactNode }) => {
+ const [user, setUser] = useState(null);
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuth = () => useContext(AuthContext);
diff --git a/package-lock.json b/package-lock.json
index 1e46666..bbd430d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@react-native-firebase/app": "^22.2.1",
"@react-native-firebase/auth": "^22.2.1",
"@react-native-firebase/messaging": "^22.2.1",
+ "@react-native-google-signin/google-signin": "^15.0.0",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"babel-preset-expo": "~13.0.0",
@@ -5488,6 +5489,22 @@
}
}
},
+ "node_modules/@react-native-google-signin/google-signin": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-15.0.0.tgz",
+ "integrity": "sha512-oU49nE+Z9TT/WaO1K7BH/QL2Nx3d2T3I5PGcYdD8swKNtfhMt8qCX/3mOnNEzthTBPrR0CWFGo3LXpkYspalog==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": ">=52.0.40",
+ "react": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.4.tgz",
diff --git a/package.json b/package.json
index 526a237..7ff2008 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"@react-native-firebase/app": "^22.2.1",
"@react-native-firebase/auth": "^22.2.1",
"@react-native-firebase/messaging": "^22.2.1",
+ "@react-native-google-signin/google-signin": "^15.0.0",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"babel-preset-expo": "~13.0.0",