Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ app-example

# Continuous Native Generation
android/
ios/
ios/

# Google Auth
google-services.json
GoogleService-Info.plist
14 changes: 14 additions & 0 deletions GoogleService-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>742231570327-s21pt82lc97lc35obefgn5nrdq4m0rn6.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.742231570327-s21pt82lc97lc35obefgn5nrdq4m0rn6</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>pt.findit.clientapp</string>
</dict>
</plist>
12 changes: 10 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"dark": "./assets/images/logos/ios-dark.png",
"light": "./assets/images/logos/ios-light.png",
"tinted": "./assets/images/logos/ios-tinted.png"
}
},
"googleServicesFile": "./GoogleService-Info.plist"
},
"android": {
"adaptiveIcon": {
Expand Down Expand Up @@ -53,6 +54,13 @@
{
"locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
}
],
"@maplibre/maplibre-react-native",
[
"@react-native-google-signin/google-signin",
{
"iosUrlScheme": "com.googleusercontent.apps.742231570327-s21pt82lc97lc35obefgn5nrdq4m0rn6"
}
]
Comment on lines +57 to 64
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Remove duplicate plugin entry and verify URL scheme configuration.

There's a duplicate @maplibre/maplibre-react-native plugin entry that should be removed. The Google Sign-In plugin configuration looks correct, but ensure the iosUrlScheme matches your Google services configuration.

       ],
-      "@maplibre/maplibre-react-native",
       [
         "@react-native-google-signin/google-signin",
         {
           "iosUrlScheme": "com.googleusercontent.apps.742231570327-s21pt82lc97lc35obefgn5nrdq4m0rn6"
         }
       ]

🏁 Script executed:

#!/bin/bash
# Verify the iOS URL scheme matches the reversed client ID from the plist
echo "Checking URL scheme consistency..."
echo "app.json iosUrlScheme:"
cat app.json | jq -r '.expo.plugins[] | select(type == "array" and .[0] == "@react-native-google-signin/google-signin") | .[1].iosUrlScheme'

echo -e "\nGoogleService-Info.plist REVERSED_CLIENT_ID:"
if command -v plutil &> /dev/null; then
  plutil -p GoogleService-Info.plist | grep -A1 "REVERSED_CLIENT_ID"
else
  grep -A1 "REVERSED_CLIENT_ID" GoogleService-Info.plist | grep -o "com\.googleusercontent\.apps\.[^<]*"
fi

Length of output: 650


Remove duplicate @maplibre/maplibre-react-native plugin entry

The Google Sign-In iosUrlScheme has been verified against your REVERSED_CLIENT_ID in GoogleService-Info.plist and is correct.

• File: app.json (around lines 57–64)
• Remove the extra "@maplibre/maplibre-react-native" entry

       ],
-      "@maplibre/maplibre-react-native",
       [
         "@react-native-google-signin/google-signin",
         {
           "iosUrlScheme": "com.googleusercontent.apps.742231570327-s21pt82lc97lc35obefgn5nrdq4m0rn6"
         }
       ]
📝 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
],
"@maplibre/maplibre-react-native",
[
"@react-native-google-signin/google-signin",
{
"iosUrlScheme": "com.googleusercontent.apps.742231570327-s21pt82lc97lc35obefgn5nrdq4m0rn6"
}
]
],
[
"@react-native-google-signin/google-signin",
{
"iosUrlScheme": "com.googleusercontent.apps.742231570327-s21pt82lc97lc35obefgn5nrdq4m0rn6"
}
]
🤖 Prompt for AI Agents
In app.json around lines 57 to 64, there is a duplicate entry of the
"@maplibre/maplibre-react-native" plugin. Remove the extra occurrence of this
plugin to avoid redundancy. The Google Sign-In plugin configuration with the
iosUrlScheme is correct and does not require changes.

],
"experiments": {
Expand All @@ -74,4 +82,4 @@
"url": "https://u.expo.dev/0bab4576-b4ae-4290-9835-f6e61e1b2bf8"
}
}
}
}
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 36 additions & 29 deletions components/LoginBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { GradientPill } from "@/components/GradientPill";
import Background from "@/components/Background";
import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
import { Platform, View } from "react-native";
import { View } from "react-native";
import { ThemedText } from "@/components/ThemedText";
import { Button } from "@/components/Button";
import AppleIcon from "@/assets/images/brands/apple.svg";
import GoogleIcon from "@/assets/images/brands/google.svg";
import React, { useRef } from "react";
import React, { useEffect, useRef } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useBackground } from "@/hooks/useBackground";
import { Redirect, useRouter } from "expo-router";
import { SharedValue } from "react-native-reanimated";
import { gql } from "@/graphql";
import { FetchResult, useMutation } from "@apollo/client";
import { UpsertUserMutation } from "@/graphql/graphql";
import { useMutation } from "@apollo/client";
import { useToast } from "@/hooks/useToast";
import { useAuth } from "@/hooks/useAuth";
import { GoogleSignin } from "@react-native-google-signin/google-signin";

interface LoginBottomSheetProps {
animatedPosition: SharedValue<number>;
Expand All @@ -24,7 +23,7 @@ interface LoginBottomSheetProps {
interface LoginMethod {
name: string;
icon: React.ComponentProps<typeof Button>["Icon"];
onPress: () => Promise<FetchResult<UpsertUserMutation>>;
onPress: () => Promise<void>;
type: "primary" | "secondary";
}

Expand All @@ -46,16 +45,11 @@ export const LoginBottomSheet = ({
const { id, setId } = useAuth();
const { toast, toastOnError } = useToast();

useEffect(() => {
GoogleSignin.configure({});
}, []);
Comment on lines +48 to +50
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Configure Google Sign-In with proper parameters.

The GoogleSignin.configure({}) call is empty, which might not provide the necessary configuration for proper functioning. Consider adding required configuration parameters.

  useEffect(() => {
-    GoogleSignin.configure({});
+    GoogleSignin.configure({
+      webClientId: 'YOUR_WEB_CLIENT_ID', // From Google Cloud Console
+      offlineAccess: true, // If you want to access Google API on behalf of the user FROM YOUR SERVER
+      hostedDomain: '', // Specifies a hosted domain restriction
+      forceCodeForRefreshToken: true, // Force user to select account on each login
+    });
  }, []);

🌐 Web query:

What are the required configuration parameters for @react-native-google-signin/google-signin version 14.0.1?

💡 Result:

To configure @react-native-google-signin/google-signin version 14.0.1, you need to call the GoogleSignin.configure() method with the appropriate parameters. The required and optional configuration parameters are as follows:

Required Parameter:

  • webClientId: This is the client ID of type WEB obtained from the Google Cloud Console. It's essential for verifying the user's ID and enabling offline access.

Optional Parameters:

  • scopes: An array of strings specifying the Google API scopes your application requires. By default, it includes email and profile. For example:
  scopes: ['https://www.googleapis.com/auth/drive.readonly']
  • offlineAccess: A boolean indicating whether to request offline access. Set to true if you need to access Google APIs on behalf of the user from your server.

  • hostedDomain: A string specifying a hosted domain restriction. If set, authorization will be restricted to accounts within the specified domain.

  • forceConsentPrompt: A boolean (Android only) that, when set to true, forces the consent prompt to appear at each login.

  • accountName: (Android only) A string specifying an account name on the device that should be used.

  • iosClientId: (iOS only) The client ID of type iOS obtained from the Google Cloud Console. If not provided, it will be taken from the GoogleService-Info.plist file.

  • googleServicePlistPath: (iOS only) The path to the GoogleService-Info.plist file if it has been renamed.

  • openIdRealm: (iOS only) The OpenID2 realm of the home web server, allowing Google to include the user's OpenID Identifier in the OpenID Connect ID token.

  • profileImageSize: (iOS only) The desired height and width of the profile image. Defaults to 120px.

Example Configuration:

import { GoogleSignin } from '@react-native-google-signin/google-signin';

GoogleSignin.configure({
  webClientId: '<YOUR_WEB_CLIENT_ID>',
  scopes: ['https://www.googleapis.com/auth/drive.readonly'],
  offlineAccess: true,
  hostedDomain: '',
  forceConsentPrompt: true,
  accountName: '',
  iosClientId: '<YOUR_IOS_CLIENT_ID>',
  googleServicePlistPath: '',
  openIdRealm: '',
  profileImageSize: 120,
});

Ensure that you replace <YOUR_WEB_CLIENT_ID> and <YOUR_IOS_CLIENT_ID> with the actual client IDs obtained from the Google Cloud Console. For detailed setup instructions and additional information, refer to the official documentation. (react-native-google-signin.github.io)

Citations:


Add necessary configuration parameters to Google Sign-In

The GoogleSignin.configure({}) call must include at least the required webClientId, and you should also specify other parameters (e.g., offlineAccess, scopes, iosClientId) as needed.

File: components/LoginBottomSheet.tsx
Lines: 48–50

Suggested change:

  useEffect(() => {
-    GoogleSignin.configure({});
+    GoogleSignin.configure({
+      webClientId: 'YOUR_WEB_CLIENT_ID',        // Required: from Google Cloud Console
+      offlineAccess: true,                      // Optional: if server-side API access is needed
+      scopes: ['email', 'profile'],             // Optional: add any additional Google API scopes
+      iosClientId: 'YOUR_IOS_CLIENT_ID',        // iOS only: from Google Cloud Console (or in plist)
+      // hostedDomain, forceConsentPrompt, accountName, etc., as needed
+    });
  }, []);

Replace placeholder IDs with your actual client IDs. For full details, see the official docs: https://react-native-google-signin.github.io/docs/api.

📝 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
useEffect(() => {
GoogleSignin.configure({});
}, []);
useEffect(() => {
GoogleSignin.configure({
webClientId: 'YOUR_WEB_CLIENT_ID', // Required: from Google Cloud Console
offlineAccess: true, // Optional: if server-side API access is needed
scopes: ['email', 'profile'], // Optional: add any additional Google API scopes
iosClientId: 'YOUR_IOS_CLIENT_ID', // iOS only: from Google Cloud Console (or in plist)
// hostedDomain, forceConsentPrompt, accountName, etc., as needed
});
}, []);
🤖 Prompt for AI Agents
In components/LoginBottomSheet.tsx around lines 48 to 50, the
GoogleSignin.configure call is missing required configuration parameters. Update
the configure call to include at least the required webClientId obtained from
the Google Cloud Console, and optionally add offlineAccess, scopes, and
iosClientId as needed. Replace placeholder client IDs with your actual client
IDs to ensure proper Google Sign-In functionality.


const [upsertUser, { loading }] = useMutation(UPSERT_USER, {
variables: {
// Temporary user data
model: {
birth_date: "2003-11-08",
email: "matilde@findit-app.pt",
first_name: "Matilde",
last_name: "Silva",
},
},
onCompleted: async (data) => {
if (!data.upsertUser) {
toast({
Expand All @@ -71,22 +65,40 @@ export const LoginBottomSheet = ({
onError: toastOnError,
});

const signIn = async () => {
try {
const signInResponse = await GoogleSignin.signIn();
const userInfo = signInResponse.data?.user;
if (signInResponse.type === "success") {
await upsertUser({
variables: {
model: {
email: userInfo?.email ?? "",
first_name: userInfo?.givenName ?? "",
last_name: userInfo?.familyName ?? "",
birth_date: "",
},
},
});
} else if (signInResponse.type === "cancelled" || !userInfo) {
toast({ title: "Sign in cancelled", text: "Please try again." });
return;
}
} catch (error) {
toast({ title: "Error signing in", text: error as string });
}
};
Comment on lines +68 to +90
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and add input validation.

The sign-in implementation looks good overall, but consider the following improvements:

  1. Error handling in the catch block should be more specific
  2. Add validation for required user fields before proceeding with upsert
  const signIn = async () => {
    try {
      const signInResponse = await GoogleSignin.signIn();
      const userInfo = signInResponse.data?.user;
      if (signInResponse.type === "success") {
+       if (!userInfo?.email) {
+         toast({ title: "Error", text: "Email is required for sign in." });
+         return;
+       }
        await upsertUser({
          variables: {
            model: {
              email: userInfo?.email ?? "",
              first_name: userInfo?.givenName ?? "",
              last_name: userInfo?.familyName ?? "",
              birth_date: "",
            },
          },
        });
      } else if (signInResponse.type === "cancelled" || !userInfo) {
        toast({ title: "Sign in cancelled", text: "Please try again." });
        return;
      }
    } catch (error) {
-     toast({ title: "Error signing in", text: error as string });
+     console.error("Google Sign-In error:", error);
+     toast({ 
+       title: "Error signing in", 
+       text: error instanceof Error ? error.message : "An unexpected error occurred. Please try again."
+     });
    }
  };
📝 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 signIn = async () => {
try {
const signInResponse = await GoogleSignin.signIn();
const userInfo = signInResponse.data?.user;
if (signInResponse.type === "success") {
await upsertUser({
variables: {
model: {
email: userInfo?.email ?? "",
first_name: userInfo?.givenName ?? "",
last_name: userInfo?.familyName ?? "",
birth_date: "",
},
},
});
} else if (signInResponse.type === "cancelled" || !userInfo) {
toast({ title: "Sign in cancelled", text: "Please try again." });
return;
}
} catch (error) {
toast({ title: "Error signing in", text: error as string });
}
};
const signIn = async () => {
try {
const signInResponse = await GoogleSignin.signIn();
const userInfo = signInResponse.data?.user;
if (signInResponse.type === "success") {
if (!userInfo?.email) {
toast({ title: "Error", text: "Email is required for sign in." });
return;
}
await upsertUser({
variables: {
model: {
email: userInfo?.email ?? "",
first_name: userInfo?.givenName ?? "",
last_name: userInfo?.familyName ?? "",
birth_date: "",
},
},
});
} else if (signInResponse.type === "cancelled" || !userInfo) {
toast({ title: "Sign in cancelled", text: "Please try again." });
return;
}
} catch (error) {
console.error("Google Sign-In error:", error);
toast({
title: "Error signing in",
text: error instanceof Error
? error.message
: "An unexpected error occurred. Please try again.",
});
}
};
🤖 Prompt for AI Agents
In components/LoginBottomSheet.tsx around lines 68 to 90, improve error handling
by checking if the caught error is an instance of Error and use its message
property for the toast instead of casting to string. Also, before calling
upsertUser, validate that required user fields like email, first_name, and
last_name are present and non-empty; if any are missing, handle this case
appropriately, such as showing a toast message and preventing the upsert call.


if (id) {
return <Redirect href={"/(tabs)"} />;
}

const methods: LoginMethod[] = [
{
name: "Apple",
icon: AppleIcon,
onPress: async () => await upsertUser(),
type: Platform.OS === "ios" ? "primary" : "secondary",
},
{
name: "Google",
icon: GoogleIcon,
onPress: async () => await upsertUser(),
type: Platform.OS === "android" ? "primary" : "secondary",
onPress: async () => await signIn(),
type: "primary",
},
];

Expand All @@ -108,12 +120,7 @@ export const LoginBottomSheet = ({
by logging in.
</ThemedText>
</View>
<View
className="flex items-center gap-4"
style={{
flexDirection: Platform.OS === "ios" ? "column" : "column-reverse",
}}
>
<View className="flex flex-col items-center gap-4">
{methods.map((method) => (
<Button
key={method.name}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@graphql-typed-document-node/core": "~3.2.0",
"@legendapp/list": "^1.0.10",
"@maplibre/maplibre-react-native": "~10.1.4",
"@react-native-google-signin/google-signin": "^14.0.1",
"@react-native-masked-view/masked-view": "~0.3.2",
"@react-native-picker/picker": "^2.11.0",
"@react-navigation/bottom-tabs": "~7.2.0",
Expand Down
Loading