Skip to content
Open
10 changes: 10 additions & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ permissions:

jobs:
preview:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -29,6 +31,14 @@ jobs:
run: npm install
working-directory: ./frontend

- name: Create .env file
working-directory: ./frontend
run: |
echo "EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID }}" >> .env
echo "EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID }}" >> .env
echo "EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID }}" >> .env
echo "EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID }}" >> .env
Comment on lines +34 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid relying on .env in CI; inject EXPO_PUBLIC_ via step env for EAS Update*

EAS Update doesn’t reliably read a local .env unless you wire a loader; safest is to pass vars via the step’s env so the bundler inlines EXPO_PUBLIC_* at build time.

Apply this diff to drop the .env creation:

-      - name: Create .env file
-        working-directory: ./frontend
-        run: |
-          echo "EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID }}" >> .env
-          echo "EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID }}" >> .env
-          echo "EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID }}" >> .env
-          echo "EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID }}" >> .env

Then, add these envs to the “Create preview” step so they’re available at bundle time:

- name: Create preview
  uses: expo/expo-github-action/preview@v8
  with:
    command: eas update --auto --branch ${{ github.event.pull_request.head.ref }}
    working-directory: ./frontend
  env:
    EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID: ${{ secrets.EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID }}
    EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID: ${{ secrets.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID }}
    EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID: ${{ secrets.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID }}
    EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID: ${{ secrets.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID }}
🤖 Prompt for AI Agents
.github/workflows/preview.yml lines 32-38: the workflow writes EXPO_PUBLIC_*
values into a local .env which EAS Update may not load at bundle time; remove
the block that creates the .env and instead add the four EXPO_PUBLIC_GOOGLE_*
secrets as environment variables on the "Create preview" step so they are
present in process env during the eas update/bundle step. Specifically delete
the "Create .env file" run step and add env: entries for
EXPO_PUBLIC_GOOGLE_EXPO_CLIENT_ID, EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID,
EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID and EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID
(sourced from the respective secrets) under the "Create preview" step.


- name: Create preview
uses: expo/expo-github-action/preview@v8
with:
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ venv/
# Firebase
firebase-service-account.json
google-services.json

# dependencies
node_modules/

Expand Down
4 changes: 4 additions & 0 deletions frontend/api/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export const signup = (name, email, password) => {
return apiClient.post("/auth/signup/email", { name, email, password });
};

export const signInWithGoogle = (id_token) => {
return apiClient.post('/auth/login/google', JSON.stringify({ id_token }));
};

export const updateUser = (userData) => apiClient.patch("/users/me", userData);

export const refresh = (refresh_token) => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/api/client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from "axios";

const API_URL = "https://splitwiser-production.up.railway.app";
const API_URL = "http://localhost:8000";

let accessToken = null;
let refreshToken = null;
Expand Down
18 changes: 12 additions & 6 deletions frontend/app.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"expo": {
"name": "frontend",
"slug": "frontend",
"name": "SplitWiser",
"scheme": "splitwiser",
"slug": "splitwiser",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"userInterfaceStyle": "automatic",
"newArchEnabled": false,
"splash": {
"image": "./assets/splash-icon.png",
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
Expand All @@ -23,12 +24,17 @@
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true,
"package": "com.splitwiser.app",
"googleServicesFile": "./google-services.json",
"permissions": ["READ_MEDIA_IMAGES"]
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-secure-store",
"expo-web-browser"
],
"extra": {
"eas": {
"projectId": "afe97159-52c4-425a-9ce3-c56d4f2cb568"
Expand Down
181 changes: 170 additions & 11 deletions frontend/context/AuthContext.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,60 @@
// AuthContext.js
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createContext, useEffect, useState } from "react";
import { Platform } from 'react-native';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import {
GoogleAuthProvider,
signInWithCredential,
signInWithPopup,
User,
} from 'firebase/auth';
Comment on lines +7 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: Importing User from firebase/auth in a JS file will crash

User is a TS type, not a runtime export. This import causes a bundling/runtime error.

 import {
   GoogleAuthProvider,
   signInWithCredential,
   signInWithPopup,
-  User,
 } from 'firebase/auth';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
GoogleAuthProvider,
signInWithCredential,
signInWithPopup,
User,
} from 'firebase/auth';
import {
GoogleAuthProvider,
signInWithCredential,
signInWithPopup,
} from 'firebase/auth';
🤖 Prompt for AI Agents
In frontend/context/AuthContext.js between lines 7 and 11, remove the import of
`User` from 'firebase/auth' because it is a TypeScript type and not available at
runtime in JavaScript, which causes bundling or runtime errors. Simply delete
`User` from the import statement to fix the issue.

import { auth } from '../firebase/firebaseConfig.web';
import * as authApi from "../api/auth";
import {
clearAuthTokens,
setAuthTokens,
setTokenUpdateListener,
} from "../api/client";

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [token, setToken] = useState(null);
const [refresh, setRefresh] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [firebaseUser, setFirebaseUser] = useState(null);
const [idToken, setIdToken] = useState(null);

// Configure Google Sign-In on component mount
useEffect(() => {
GoogleSignin.configure({
webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
offlineAccess: true,
});
}, []);
Comment on lines +29 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider adding platform-specific client IDs to GoogleSignin.configure

For the native flow, many setups require iosClientId and/or androidClientId in addition to webClientId to ensure proper token issuance. Confirm your Google credentials and add as needed.

Example (only if your credentials exist):

   useEffect(() => {
       GoogleSignin.configure({
-        webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
+        webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
+        iosClientId: process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID,
+        androidClientId: process.env.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID,
         offlineAccess: true,
       });
   }, []);

To verify repo config includes the required plugin and env wiring, run:


🏁 Script executed:

#!/bin/bash
# Check app config and credentials presence
fd -a 'app.(json|config.*)' | xargs -I{} sh -c 'echo "--- {} ---"; cat "{}"'
rg -n "react-native-google-signin|google-services|EXPO_PUBLIC_GOOGLE_(WEB|IOS|ANDROID)_CLIENT_ID|expo-secure-store|expo-web-browser" -A 3

Length of output: 5366


Add iOS and Android client IDs to GoogleSignin.configure

To ensure native flows on both platforms issue the correct tokens, include iosClientId and androidClientId alongside your existing webClientId. You’ll also want to verify that these variables are defined in your environment (e.g. in .env or via Expo’s extra), and exposed as EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID and EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID.

Locations to update:

  • frontend/context/AuthContext.js (around lines 29–35)
  • Your environment definition (.env, app.config.js, or wherever you expose Expo “extra”)

Example diff:

--- a/frontend/context/AuthContext.js
+++ b/frontend/context/AuthContext.js
@@ useEffect(() => {
-  GoogleSignin.configure({
-    webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
-    offlineAccess: true,
-  });
+  GoogleSignin.configure({
+    webClientId:   process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
+    iosClientId:   process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID,
+    androidClientId: process.env.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID,
+    offlineAccess: true,
+  });

Quick environment check:

# Ensure your .env or Expo config exposes the IDs
grep -R "EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID" -n .
grep -R "EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID" -n .
🤖 Prompt for AI Agents
In frontend/context/AuthContext.js around lines 29 to 35, update the
GoogleSignin.configure call to include iosClientId and androidClientId
properties, sourcing their values from environment variables
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID and EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID
respectively. Also, verify that these environment variables are properly defined
and exposed in your .env file or Expo configuration (such as app.config.js) to
ensure native platform flows receive the correct tokens.



// Load token and user data from AsyncStorage on app start
useEffect(() => {
const loadStoredAuth = async () => {
try {
const storedToken = await AsyncStorage.getItem("auth_token");
const storedToken = await AsyncStorage.getItem('auth_token');
const storedUser = await AsyncStorage.getItem('user_data');
const storedRefresh = await AsyncStorage.getItem("refresh_token");
const storedUser = await AsyncStorage.getItem("user_data");
const storedIdToken = await AsyncStorage.getItem('firebase_id_token');
const storedFirebaseUser = await AsyncStorage.getItem('firebase_user');

Comment on lines +42 to 47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Load refresh/idToken/firebaseUser; remove duplicate setUser

Currently:

  • setUser is called twice.
  • refresh isn’t restored into state.
  • Firebase ID token and user aren’t restored at startup.

Fix all three.

-        const storedToken = await AsyncStorage.getItem('auth_token');
-        const storedUser = await AsyncStorage.getItem('user_data');
+        const storedToken = await AsyncStorage.getItem('auth_token');
+        const storedUser = await AsyncStorage.getItem('user_data');
         const storedRefresh = await AsyncStorage.getItem("refresh_token");
         const storedIdToken = await AsyncStorage.getItem('firebase_id_token');
         const storedFirebaseUser = await AsyncStorage.getItem('firebase_user');
 
-        if (storedToken && storedUser) {
-          setToken(storedToken);
-          setUser(JSON.parse(storedUser));
-          await setAuthTokens({
-            newAccessToken: storedToken,
-            newRefreshToken: storedRefresh,
-          });
-          // Normalize user id shape: ensure `_id` exists even if API stored `id`
-          const parsed = JSON.parse(storedUser);
-          const normalized = parsed?._id
-            ? parsed
-            : parsed?.id
-            ? { ...parsed, _id: parsed.id }
-            : parsed;
-          setUser(normalized);
-        }
+        // Restore Firebase auth data (if present)
+        if (storedIdToken) setIdToken(storedIdToken);
+        if (storedFirebaseUser) {
+          try {
+            setFirebaseUser(JSON.parse(storedFirebaseUser));
+          } catch {
+            // ignore parse error; storage may contain older format
+          }
+        }
+
+        // Restore app tokens and user
+        if (storedToken && storedUser) {
+          setToken(storedToken);
+          if (storedRefresh) setRefresh(storedRefresh);
+          await setAuthTokens({
+            newAccessToken: storedToken,
+            newRefreshToken: storedRefresh,
+          });
+          // Normalize user id shape: ensure `_id` exists even if API stored `id`
+          const parsed = JSON.parse(storedUser);
+          const normalized = parsed?._id
+            ? parsed
+            : parsed?.id
+            ? { ...parsed, _id: parsed.id }
+            : parsed;
+          setUser(normalized);
+        }

Also applies to: 49-63

🤖 Prompt for AI Agents
In frontend/context/AuthContext.js around lines 42 to 47 and 49 to 63, fix the
issue where setUser is called twice by removing the duplicate call, ensure the
refresh token is restored into state by setting it appropriately, and restore
the Firebase ID token and Firebase user into state at startup by retrieving and
setting them correctly. This will consolidate state initialization and avoid
redundant updates.

if (storedToken && storedUser) {
if (storedFirebaseUser) {
try {
setFirebaseUser(JSON.parse(storedFirebaseUser));
} catch {
// ignore parse error; storage may contain older format
}
}
setToken(storedToken);
setRefresh(storedRefresh);
setUser(JSON.parse(storedUser));
await setAuthTokens({
newAccessToken: storedToken,
newRefreshToken: storedRefresh,
Expand Down Expand Up @@ -106,8 +135,33 @@ export const AuthProvider = ({ children }) => {
saveUser();
}, [user]);

// Save Firebase data to AsyncStorage
useEffect(() => {
const saveFirebaseData = async () => {
try {
if (idToken) {
await AsyncStorage.setItem('firebase_id_token', idToken);
} else {
await AsyncStorage.removeItem('firebase_id_token');
}

if (firebaseUser) {
await AsyncStorage.setItem('firebase_user', JSON.stringify(firebaseUser));
} else {
await AsyncStorage.removeItem('firebase_user');
}
} catch (error) {
console.error('Failed to save Firebase data to storage:', error);
}
};

saveFirebaseData();
}, [idToken, firebaseUser]);
Comment on lines +138 to +159
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid JSON.stringify on Firebase User; store a safe subset

Firebase User contains circular references. Stringifying it will throw. Persist a minimal, serializable snapshot instead.

-        if (firebaseUser) {
-          await AsyncStorage.setItem('firebase_user', JSON.stringify(firebaseUser));
-        } else {
+        if (firebaseUser) {
+          const snapshot = {
+            uid: firebaseUser.uid,
+            displayName: firebaseUser.displayName ?? null,
+            email: firebaseUser.email ?? null,
+            photoURL: firebaseUser.photoURL ?? null,
+            providerData: (firebaseUser.providerData || []).map(p => ({
+              providerId: p?.providerId,
+              uid: p?.uid,
+              displayName: p?.displayName ?? null,
+              email: p?.email ?? null,
+              photoURL: p?.photoURL ?? null,
+            })),
+          };
+          await AsyncStorage.setItem('firebase_user', JSON.stringify(snapshot));
+        } else {
           await AsyncStorage.removeItem('firebase_user');
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Save Firebase data to AsyncStorage
useEffect(() => {
const saveFirebaseData = async () => {
try {
if (idToken) {
await AsyncStorage.setItem('firebase_id_token', idToken);
} else {
await AsyncStorage.removeItem('firebase_id_token');
}
if (firebaseUser) {
await AsyncStorage.setItem('firebase_user', JSON.stringify(firebaseUser));
} else {
await AsyncStorage.removeItem('firebase_user');
}
} catch (error) {
console.error('Failed to save Firebase data to storage:', error);
}
};
saveFirebaseData();
}, [idToken, firebaseUser]);
// Save Firebase data to AsyncStorage
useEffect(() => {
const saveFirebaseData = async () => {
try {
if (idToken) {
await AsyncStorage.setItem('firebase_id_token', idToken);
} else {
await AsyncStorage.removeItem('firebase_id_token');
}
if (firebaseUser) {
const snapshot = {
uid: firebaseUser.uid,
displayName: firebaseUser.displayName ?? null,
email: firebaseUser.email ?? null,
photoURL: firebaseUser.photoURL ?? null,
providerData: (firebaseUser.providerData || []).map(p => ({
providerId: p?.providerId,
uid: p?.uid,
displayName: p?.displayName ?? null,
email: p?.email ?? null,
photoURL: p?.photoURL ?? null,
})),
};
await AsyncStorage.setItem('firebase_user', JSON.stringify(snapshot));
} else {
await AsyncStorage.removeItem('firebase_user');
}
} catch (error) {
console.error('Failed to save Firebase data to storage:', error);
}
};
saveFirebaseData();
}, [idToken, firebaseUser]);
🤖 Prompt for AI Agents
In frontend/context/AuthContext.js around lines 131 to 152, the code attempts to
JSON.stringify the entire Firebase user object, which contains circular
references and causes errors. To fix this, extract and store only a minimal,
serializable subset of the Firebase user data (such as uid, email, displayName)
instead of the full user object before saving it to AsyncStorage.


// Regular email/password login
const login = async (email, password) => {
try {

const response = await authApi.login(email, password);
const { access_token, refresh_token, user: userData } = response.data;
setToken(access_token);
Expand All @@ -133,6 +187,7 @@ export const AuthProvider = ({ children }) => {
}
};

// Regular email/password signup
const signup = async (name, email, password) => {
try {
await authApi.signup(name, email, password);
Expand All @@ -146,20 +201,91 @@ export const AuthProvider = ({ children }) => {
}
};

const logout = async () => {
try {
// Clear stored authentication data
await AsyncStorage.removeItem("auth_token");
await AsyncStorage.removeItem("refresh_token");
await AsyncStorage.removeItem("user_data");
// Google Sign-In
const signInWithGoogle = async () => {
try {
if (Platform.OS === 'web') {
// ---- WEB FLOW ----
const provider = new GoogleAuthProvider();
const result = await signInWithPopup(auth, provider);

const firebaseIdToken = await result.user.getIdToken(true);
setFirebaseUser(result.user);
setIdToken(firebaseIdToken);

// Exchange with backend for app tokens
const backendResponse = await authApi.signInWithGoogle(firebaseIdToken);
const { access_token, refresh_token, user: userData } = backendResponse.data;
setToken(access_token);
if (refresh_token) setRefresh(refresh_token);
await setAuthTokens({
newAccessToken: access_token,
newRefreshToken: refresh_token,
});
setUser(userData);

return { idToken: firebaseIdToken, firebaseUser: result.user };
} else {
// ---- NATIVE FLOW ----
await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });

const { idToken: googleIdToken } = await GoogleSignin.signIn();
if (!googleIdToken) throw new Error('No Google ID token returned');

const credential = GoogleAuthProvider.credential(googleIdToken);
const userCredential = await signInWithCredential(auth, credential);

const firebaseIdToken = await userCredential.user.getIdToken(true);
setFirebaseUser(userCredential.user);
setIdToken(firebaseIdToken);

const backendResponse = await authApi.signInWithGoogle(firebaseIdToken);
const { access_token, refresh_token, user: userData } = backendResponse.data;
setToken(access_token);
if (refresh_token) setRefresh(refresh_token);
await setAuthTokens({
newAccessToken: access_token,
newRefreshToken: refresh_token,
});
setUser(userData);

return { idToken: firebaseIdToken, firebaseUser: userCredential.user };
}
} catch (error) {
console.error('Google Sign-In error:', error?.message || error);
throw error;
}
};

// Unified logout function
const logout = async () => {
try {
// Clear stored authentication data
await AsyncStorage.removeItem('auth_token');
await AsyncStorage.removeItem('user_data');
await AsyncStorage.removeItem('firebase_id_token');
await AsyncStorage.removeItem('firebase_user');

// Sign out from Google/Firebase if applicable
if (firebaseUser) {
if (Platform.OS === 'web') {
await auth.signOut();
} else {
await GoogleSignin.signOut();
await auth.signOut();
}
}
} catch (error) {
console.error("Failed to clear stored authentication:", error);
}

// Clear all state
setToken(null);
setRefresh(null);
setUser(null);
await clearAuthTokens();
setFirebaseUser(null);
setIdToken(null);
};
Comment on lines +260 to 289
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Logout should also remove the refresh token from storage

refresh_token remains in AsyncStorage after logout. Clear it to avoid stale re-auth attempts.

Apply this diff:

-     await AsyncStorage.removeItem('auth_token');
-      await AsyncStorage.removeItem('user_data');
-      await AsyncStorage.removeItem('firebase_id_token');
-      await AsyncStorage.removeItem('firebase_user');
+      await AsyncStorage.removeItem('auth_token');
+      await AsyncStorage.removeItem('refresh_token');
+      await AsyncStorage.removeItem('user_data');
+      await AsyncStorage.removeItem('firebase_id_token');
+      await AsyncStorage.removeItem('firebase_user');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Unified logout function
const logout = async () => {
try {
// Clear stored authentication data
await AsyncStorage.removeItem('auth_token');
await AsyncStorage.removeItem('user_data');
await AsyncStorage.removeItem('firebase_id_token');
await AsyncStorage.removeItem('firebase_user');
// Sign out from Google/Firebase if applicable
if (firebaseUser) {
if (Platform.OS === 'web') {
await auth.signOut();
} else {
await GoogleSignin.signOut();
await auth.signOut();
}
}
} catch (error) {
console.error("Failed to clear stored authentication:", error);
}
// Clear all state
setToken(null);
setRefresh(null);
setUser(null);
await clearAuthTokens();
setFirebaseUser(null);
setIdToken(null);
};
// Unified logout function
const logout = async () => {
try {
// Clear stored authentication data
await AsyncStorage.removeItem('auth_token');
await AsyncStorage.removeItem('refresh_token');
await AsyncStorage.removeItem('user_data');
await AsyncStorage.removeItem('firebase_id_token');
await AsyncStorage.removeItem('firebase_user');
// Sign out from Google/Firebase if applicable
if (firebaseUser) {
if (Platform.OS === 'web') {
await auth.signOut();
} else {
await GoogleSignin.signOut();
await auth.signOut();
}
}
} catch (error) {
console.error("Failed to clear stored authentication:", error);
}
// Clear all state
setToken(null);
setRefresh(null);
setUser(null);
await clearAuthTokens();
setFirebaseUser(null);
setIdToken(null);
};
🤖 Prompt for AI Agents
In frontend/context/AuthContext.js between lines 260 and 289, the logout
function clears several authentication items from AsyncStorage but does not
remove the refresh token. To fix this, add an await AsyncStorage.removeItem call
for the 'refresh_token' key alongside the other removeItem calls to ensure the
refresh token is also cleared during logout.


const updateUserInContext = (updatedUser) => {
Expand All @@ -172,19 +298,52 @@ export const AuthProvider = ({ children }) => {
setUser(normalizedUser);
};

// Helper function to check if user is authenticated (either way)
const isAuthenticated = () => {
return !!(token || idToken);
};

// Helper function to get current auth method
const getAuthMethod = () => {
if (idToken && firebaseUser) return 'google';
if (token && user) return 'email';
return null;
};

return (
<AuthContext.Provider

value={{

// Original auth state
user,

token,

isLoading,


// Google auth state
firebaseUser,
idToken,

// Auth methods
login,

signup,
signInWithGoogle,

logout,

updateUserInContext,

// Helper methods
isAuthenticated,
getAuthMethod,
}}

>
{children}
</AuthContext.Provider>
);
};
};
7 changes: 5 additions & 2 deletions frontend/eas.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"cli": {
"version": ">= 7.6.0"
"version": ">= 16.17.3",
"appVersionSource": "remote"
},
"build": {
"development": {
Expand All @@ -10,7 +11,9 @@
"preview": {
"distribution": "internal"
},
"production": {}
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
Expand Down
24 changes: 24 additions & 0 deletions frontend/firebase/firebaseConfig.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { initializeApp, getApp, getApps } from "firebase/app";
import { initializeAuth, GoogleAuthProvider } from "firebase/auth";
import { getReactNativePersistence } from "firebase/auth/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";

const firebaseConfig = {
apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
measurementId: process.env.EXPO_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const app = getApps().length ? getApp() : initializeApp(firebaseConfig);

const auth = initializeAuth(app, {
persistence: getReactNativePersistence(AsyncStorage),
});

Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard initializeAuth to avoid RN Auth re-initialization errors (HMR/dev reload)

Calling initializeAuth more than once throws (e.g., during fast refresh). Use a guard and fall back to getAuth after first init.

Apply this diff:

-import { initializeAuth, GoogleAuthProvider } from "firebase/auth";
+import { initializeAuth, GoogleAuthProvider, getAuth } from "firebase/auth";
@@
-const auth = initializeAuth(app, {
-  persistence: getReactNativePersistence(AsyncStorage),
-});
+// Avoid double-initialization across HMR/fast refresh
+const getOrInitAuth = () => {
+  if (!globalThis.__FIREBASE_AUTH_INITIALIZED__) {
+    const inst = initializeAuth(app, {
+      persistence: getReactNativePersistence(AsyncStorage),
+    });
+    globalThis.__FIREBASE_AUTH_INITIALIZED__ = true;
+    return inst;
+  }
+  return getAuth(app);
+};
+
+const auth = getOrInitAuth();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const auth = initializeAuth(app, {
persistence: getReactNativePersistence(AsyncStorage),
});
// Import (add getAuth)
import { initializeAuth, GoogleAuthProvider, getAuth } from "firebase/auth";
import { getReactNativePersistence } from "firebase/auth/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
// …other imports…
// Avoid double-initialization across HMR/fast refresh
const getOrInitAuth = () => {
if (!globalThis.__FIREBASE_AUTH_INITIALIZED__) {
const inst = initializeAuth(app, {
persistence: getReactNativePersistence(AsyncStorage),
});
globalThis.__FIREBASE_AUTH_INITIALIZED__ = true;
return inst;
}
return getAuth(app);
};
const auth = getOrInitAuth();
// …rest of your config…
🤖 Prompt for AI Agents
In frontend/firebase/firebaseConfig.native.js around lines 18 to 21, calling
initializeAuth multiple times causes errors during hot module reload or fast
refresh. To fix this, add a guard that checks if an auth instance already
exists; if it does, use getAuth(app) instead of calling initializeAuth again.
This prevents re-initialization errors by reusing the existing auth instance.

const googleProvider = new GoogleAuthProvider();

export { app, auth, googleProvider };
Loading
Loading