Fixes and tickets

This commit is contained in:
Thomas Demirdjian
2025-10-28 15:43:18 +01:00
parent 2599a25d77
commit d1be3c19a2
31 changed files with 6663 additions and 795 deletions
+100 -233
View File
@@ -1,18 +1,7 @@
/* eslint-disable react/display-name */
import { GoogleSigninButton } from "@react-native-google-signin/google-signin";
import * as AuthSession from "expo-auth-session";
import * as GoogleAuth from "expo-auth-session/providers/google";
import Constants from "expo-constants";
import * as WebBrowser from "expo-web-browser";
import React, { useEffect, useState } from "react";
import {
ActivityIndicator,
Platform,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
import * as AppleAuthentication from "expo-apple-authentication";
import React, { useCallback, useState } from "react";
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from "react-native";
import Svg, { Path } from "react-native-svg";
import { useGlobal } from "reactn";
import { handleFirebaseError } from "../actions/signupActions.js";
@@ -21,11 +10,7 @@ import GradientButton from "../components/GradientButton.js";
import { Input } from "../components/Input.js";
import ItemContainer from "../components/ItemContainer/ItemContainer";
import firebase from "../config/firebase";
import {
GOOGLE_ANDROID_CLIENT_ID,
GOOGLE_IOS_CLIENT_ID,
GOOGLE_WEB_CLIENT_ID,
} from "../data/keys";
import useSocialAuth from "../hooks/useSocialAuth";
import { isWeb } from "../hooks/useLayoutType";
import Page from "../layouts/Page.js";
import { Routes } from "../navigation";
@@ -34,37 +19,26 @@ import { FONT_FAMILY } from "../styles/Fonts.js";
import Palette from "../styles/Palette.js";
export default ({ navigation }) => {
WebBrowser.maybeCompleteAuthSession();
const [, setTooltip] = useGlobal("_tooltip");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
// Let the provider compute a compliant redirect URI for native (com.googleusercontent.apps.<client-id>:/oauth2redirect)
// Avoid forcing a custom scheme like musicland:// which Google can reject for native apps.
const isExpoGo = Constants?.appOwnership === "expo";
const expoRedirectUri = AuthSession.makeRedirectUri({ useProxy: true });
const nativeRedirectUri = getNativeGoogleRedirectUri();
const redirectUri =
isWeb || isExpoGo ? expoRedirectUri : nativeRedirectUri || expoRedirectUri;
const [request, response, promptAsync] = GoogleAuth.useAuthRequest({
iosClientId: GOOGLE_IOS_CLIENT_ID,
androidClientId: GOOGLE_ANDROID_CLIENT_ID,
webClientId: GOOGLE_WEB_CLIENT_ID,
expoClientId: GOOGLE_WEB_CLIENT_ID,
redirectUri,
// Use Authorization Code + PKCE to comply with Google OAuth for native apps
responseType: AuthSession.ResponseType.Code,
usePKCE: true,
scopes: ["openid", "profile", "email"],
});
const afterLoginNavigate = async () => {
const afterLoginNavigate = useCallback(async () => {
const uid = firebase.auth().currentUser?.uid;
if (!uid) throw new Error("Aucun utilisateur après connexion");
navigation.reset({ index: 0, routes: [{ name: Routes.BottomTab }] });
};
}, [navigation]);
const {
signInWithGoogle,
signInWithApple,
isAppleSignInAvailable,
isLoading: socialLoading,
} = useSocialAuth({
onSuccess: afterLoginNavigate,
});
const isBusy = loading || socialLoading;
const onLogin = async () => {
try {
@@ -83,110 +57,6 @@ export default ({ navigation }) => {
}
};
const onLoginWithGoogle = async () => {
try {
setLoading(true);
if (isWeb) {
const provider = new firebase.auth.GoogleAuthProvider();
provider.addScope("profile");
provider.addScope("email");
await firebase.auth().signInWithPopup(provider);
await afterLoginNavigate();
setLoading(false);
} else {
// Use defaults from the request; don't override with proxy here
const result = await promptAsync({ useProxy: isExpoGo });
if (result?.type !== "success") {
// Cancelled or errored during the browser flow
setLoading(false);
}
}
} catch (e) {
console.log("Google Login error", e?.message);
const message =
e?.code && e.code.startsWith("auth/")
? handleFirebaseError(e.code)
: e?.message;
setTooltip({
text: message || "Connexion Google impossible. Merci de réessayer.",
type: "error",
});
setLoading(false);
}
};
// Handle native Google OAuth response
useEffect(() => {
const handleNativeGoogleResponse = async () => {
try {
if (response?.type === "success") {
// If using Expo proxy, tokens can be present already.
let idToken =
response?.authentication?.idToken || response?.params?.id_token;
if (!idToken) {
// Otherwise, exchange the authorization code for tokens using PKCE.
const code =
response?.params?.code || response?.authentication?.code;
if (!code) throw new Error("Code d'autorisation Google manquant");
// Use the exact clientId used during the authorize request.
const clientId = request?.clientId;
const discovery = {
authorizationEndpoint:
"https://accounts.google.com/o/oauth2/v2/auth",
tokenEndpoint: "https://oauth2.googleapis.com/token",
revocationEndpoint: "https://oauth2.googleapis.com/revoke",
};
// Light debug: helps identify redirect/client mismatches in dev.
console.log("Google token exchange", {
clientId,
redirectUri: request?.redirectUri,
hasCodeVerifier: !!request?.codeVerifier,
});
const tokenResponse = await AuthSession.exchangeCodeAsync(
{
clientId,
code,
redirectUri: request?.redirectUri,
extraParams: { code_verifier: request?.codeVerifier },
},
discovery
);
idToken = tokenResponse?.id_token;
}
if (!idToken)
throw new Error("Jeton Google manquant (échange/retour)");
const credential =
firebase.auth.GoogleAuthProvider.credential(idToken);
await firebase.auth().signInWithCredential(credential);
await afterLoginNavigate();
}
} catch (e) {
console.log("Google native sign-in error", e?.message);
const message =
e?.code && e.code.startsWith("auth/")
? handleFirebaseError(e.code)
: e?.message;
setTooltip({
text: message || "Connexion Google impossible",
type: "error",
});
} finally {
setLoading(false);
}
};
if (!isWeb && response) {
handleNativeGoogleResponse();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [response]);
return (
<Page
width={isWeb ? 600 : null}
@@ -241,97 +111,95 @@ export default ({ navigation }) => {
<GradientButton
title="Se connecter"
containerStyle={{
width: "80%",
width: "90%",
alignSelf: "center",
}}
onPress={onLogin}
disabled={loading || !email || !password}
disabled={isBusy || !email || !password}
/>
<GoogleAuthButton
onPress={onLoginWithGoogle}
disabled={loading}
loading={loading}
/>
<View style={styles.signupWrapper}>
<Text style={styles.signupPrompt}>Pas encore de compte ?</Text>
<Pressable
onPress={() => navigate(Routes.Register)}
style={({ pressed }) => [
styles.signupPressable,
pressed && styles.linkPressed,
]}
>
<Text style={styles.signupLink}>Créer un compte</Text>
</Pressable>
<View style={styles.socialWrapper}>
{isAppleSignInAvailable ? (
<AppleAuthButton
onPress={signInWithApple}
disabled={isBusy}
loading={socialLoading}
/>
) : null}
<GoogleAuthButton
onPress={signInWithGoogle}
disabled={isBusy}
loading={socialLoading}
/>
</View>
</View>
</ItemContainer>
<View style={styles.bottomFooter}>
<Text style={styles.bottomFooterText}>
Tu n'as pas de compte ?{" "}
<Text
style={styles.bottomFooterLink}
onPress={() => navigate(Routes.Register)}
>
Créer un compte
</Text>
</Text>
</View>
</View>
</Page>
);
};
const GoogleAuthButton = ({ onPress, disabled, loading }) => {
const AppleAuthButton = ({ onPress, disabled, loading }) => {
if (isWeb) {
return (
<Pressable
accessibilityRole="button"
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.webButton,
pressed && !disabled ? styles.webButtonPressed : null,
disabled ? styles.webButtonDisabled : null,
]}
>
<View style={styles.webContent}>
<View style={styles.webIconContainer}>
<GoogleLogo />
</View>
<Text style={styles.webText}>Continuer avec Google</Text>
{loading ? (
<ActivityIndicator size="small" color="#4285F4" />
) : (
<View style={styles.webRightSpacer} />
)}
</View>
</Pressable>
);
return null;
}
return (
<View style={styles.nativeButtonWrapper}>
<GoogleSigninButton
<AppleAuthentication.AppleAuthenticationButton
buttonType={AppleAuthentication.AppleAuthenticationButtonType.CONTINUE}
buttonStyle={
AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
}
style={styles.nativeButton}
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Dark}
cornerRadius={12}
onPress={onPress}
disabled={disabled}
/>
{loading ? (
<View style={styles.nativeLoadingOverlay}>
<ActivityIndicator size="small" color={Palette.white} />
<ActivityIndicator size="small" color={Palette.black} />
</View>
) : null}
</View>
);
};
const getNativeGoogleRedirectUri = () => {
const clientId = Platform.select({
ios: GOOGLE_IOS_CLIENT_ID,
android: GOOGLE_ANDROID_CLIENT_ID,
default: null,
});
if (!clientId || !clientId.includes(".apps.googleusercontent.com")) {
return undefined;
}
const clientPrefix = clientId.replace(".apps.googleusercontent.com", "");
return `com.googleusercontent.apps.${clientPrefix}:/oauth2redirect`;
};
const GoogleAuthButton = ({ onPress, disabled, loading }) => (
<Pressable
accessibilityRole="button"
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.socialButton,
pressed && !disabled ? styles.socialButtonPressed : null,
disabled ? styles.socialButtonDisabled : null,
]}
>
<View style={styles.socialButtonContent}>
<View style={styles.socialIconContainer}>
<GoogleLogo />
</View>
<Text style={styles.socialButtonText}>Continuer avec Google</Text>
{loading ? (
<ActivityIndicator size="small" color="#4285F4" />
) : (
<View style={styles.socialRightSpacer} />
)}
</View>
</Pressable>
);
const styles = StyleSheet.create({
screen: {
@@ -347,6 +215,7 @@ const styles = StyleSheet.create({
cardContent: {
gap: 28,
paddingTop: 8,
paddingHorizontal: 5,
width: "100%",
},
greetingSection: {
@@ -384,31 +253,29 @@ const styles = StyleSheet.create({
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "right",
},
signupWrapper: {
flexDirection: "row",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "center",
gap: 6,
bottomFooter: {
marginTop: 16,
paddingHorizontal: 24,
},
signupPrompt: {
bottomFooterText: {
fontSize: 14,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
fontFamily: FONT_FAMILY.HelveticaNeueRegular,
textAlign: "center",
},
signupPressable: {
paddingHorizontal: 2,
},
signupLink: {
fontSize: 14,
color: Palette.white,
bottomFooterLink: {
fontFamily: FONT_FAMILY.InterSemiBold,
color: Palette.white,
},
webButton: {
socialWrapper: {
width: "100%",
gap: 16,
alignItems: "center",
},
socialButton: {
alignSelf: "center",
borderRadius: 12,
width: "80%",
width: "90%",
backgroundColor: Palette.white,
borderWidth: 1,
borderColor: "#E3E7EF",
@@ -420,38 +287,38 @@ const styles = StyleSheet.create({
shadowRadius: 16,
elevation: 2,
},
webButtonPressed: {
socialButtonPressed: {
backgroundColor: "#F4F7FF",
},
webButtonDisabled: {
socialButtonDisabled: {
opacity: 0.7,
},
webContent: {
socialButtonContent: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: 12,
},
webIconContainer: {
socialIconContainer: {
justifyContent: "center",
alignItems: "center",
width: 28,
height: 28,
},
webText: {
socialButtonText: {
flex: 1,
textAlign: "center",
fontSize: 15,
color: "#202124",
fontFamily: FONT_FAMILY.InterSemiBold,
},
webRightSpacer: {
socialRightSpacer: {
width: 24,
height: 24,
},
nativeButtonWrapper: {
alignSelf: "center",
width: "80%",
width: "90%",
height: 50,
justifyContent: "center",
},