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",