/** * Auth Services - Platform-aware authentication * Works on both native (Android/iOS) and web platforms */ import { Platform } from "react-native"; import { signInWithGoogle as firebaseSignInWithGoogle, signOutFromGoogle as firebaseSignOutFromGoogle, signInWithPhoneNumber as firebaseSignInWithPhoneNumber, createUserWithEmailAndPassword as firebaseCreateUserWithEmailAndPassword, signInWithEmailAndPassword as firebaseSignInWithEmailAndPassword, collection, doc, FieldValue, isWeb, } from "../firebase"; import { formatPhoneNumber } from "../utils/phoneUtils"; import { useUserProfileStore } from "../stores/userProfileStore"; import { withGlobalLoading } from "../stores/uiStore"; // Types interface PhoneAuthResult { confirmationResult?: any; error?: string; } interface OTPVerificationResult { user: any | null; error?: string; } interface GoogleSignInResult { user: any | null; isNewUser: boolean; error?: string; } export interface UserLinkedAccount { id: string; bankId: string; bankName: string; accountNumber: string; isDefault: boolean; } export interface UserProfile { uid: string; email: string; fullName: string; phoneNumber?: string; address?: string; pin?: string; signupType?: "phone" | "google"; photoUrl?: string; fanNumber?: string; tin?: string; businessType?: string; nationalIdUrl?: string; businessLicenseUrl?: string; linkedAccounts?: UserLinkedAccount[]; createdAt: any; updatedAt: any; } export class AuthService { // Google Sign-In - Platform aware static async signInWithGoogle(): Promise { try { const result = await firebaseSignInWithGoogle(); if (result.error) { return { user: null, isNewUser: false, error: result.error }; } console.log( "Google Sign-In successful, user:", result.user?.uid, "isNewUser:", result.isNewUser ); // Log ID token if available (for debugging / backend integration) try { if ( result.user && typeof (result.user as any).getIdToken === "function" ) { const idToken = await (result.user as any).getIdToken(); console.log("GOOGLE SIGN-IN ID TOKEN:", idToken); } } catch (tokenError) { console.warn("Failed to get Google ID token:", tokenError); } return result; } catch (error: any) { console.error("Google Sign-In error:", error); return { user: null, isNewUser: false, error: error.message || "Google Sign-In failed", }; } } // Sign out from Google static async signOutFromGoogle(): Promise { try { await firebaseSignOutFromGoogle(); } catch (error) { console.error("Google Sign-Out error:", error); } } // Phone number authentication - send OTP static async sendOTP(phoneNumber: string): Promise { try { // Validate and format phone number const formattedPhoneNumber = formatPhoneNumber(phoneNumber); console.log("FORMATTED PHONE NUMBER:", formattedPhoneNumber); if (!formattedPhoneNumber) { return { error: "Invalid phone number format. Please use format: +1234567890", }; } console.log("Sending OTP to:", formattedPhoneNumber); // Web requires reCAPTCHA initialization first if (isWeb) { // Check if reCAPTCHA container exists const recaptchaContainer = document.getElementById( "recaptcha-container" ); if (!recaptchaContainer) { console.warn( "reCAPTCHA container not found. Phone auth may not work." ); return { error: "Phone authentication not available. Please use Google Sign-In.", }; } // Initialize reCAPTCHA if needed const { initRecaptcha } = await import("../firebase/firebase.web"); await initRecaptcha("recaptcha-container"); } const confirmationResult = await firebaseSignInWithPhoneNumber( formattedPhoneNumber ); return { confirmationResult }; } catch (error) { console.error("Phone auth error:", error); return { error: error instanceof Error ? error.message : "Failed to send OTP", }; } } // Verify OTP code static async verifyOTP( confirmationResult: any, code: string ): Promise { try { const result = await confirmationResult.confirm(code); const user = result?.user ?? null; if (user) { console.log("PHONE AUTH SUCCESS, UID:", user.uid); try { if (typeof (user as any).getIdToken === "function") { const idToken = await (user as any).getIdToken(); console.log("PHONE AUTH ID TOKEN:", idToken); } } catch (tokenError) { console.warn("Failed to get phone auth ID token:", tokenError); } } return { user }; } catch (error) { console.error("OTP verification error:", error); return { user: null, error: error instanceof Error ? error.message : "Invalid verification code", }; } } // Update User Profile static async updateUserProfile( uid: string, profileData: { fullName?: string; phoneNumber?: string; address?: string; pin?: string; photoUrl?: string; fanNumber?: string; tin?: string; businessType?: string; nationalIdUrl?: string; businessLicenseUrl?: string; linkedAccounts?: UserLinkedAccount[]; } ): Promise<{ success: boolean; error?: string }> { return withGlobalLoading(async () => { try { const userDocRef = doc("users", uid); const updateData: any = { ...profileData, updatedAt: FieldValue.serverTimestamp(), }; // Remove undefined values Object.keys(updateData).forEach((key) => { if (updateData[key] === undefined) { delete updateData[key]; } }); console.log("[AuthService.updateUserProfile] Updating user profile", { uid, updateData, }); await userDocRef.update(updateData); console.log( "[AuthService.updateUserProfile] Profile updated successfully" ); try { await useUserProfileStore.getState().invalidateProfile(uid); } catch (cacheError) { console.warn( "Failed to refresh user profile cache after update", cacheError ); } return { success: true }; } catch (error) { console.error( "[AuthService.updateUserProfile] Profile update error for uid", uid, error ); return { success: false, error: error instanceof Error ? error.message : "Failed to update profile", }; } }); } // Check if user profile exists in Firestore static async checkUserProfileExists(uid: string): Promise { try { const userDocRef = doc("users", uid); const userDoc = await userDocRef.get(); // Handle both boolean and function forms of exists const existsValue = userDoc.exists; return typeof existsValue === "function" ? existsValue() : !!existsValue; } catch (error) { console.error("Error checking user profile:", error); return false; } } // Create user profile in Firestore static async createUserProfile( uid: string, profileData: { fullName: string; phoneNumber?: string; address?: string; email?: string; pin?: string; signupType?: "phone" | "google" | "email"; createdAt: Date; updatedAt: Date; photoUrl?: string; } ): Promise { return withGlobalLoading(async () => { try { console.log("Creating user profile in Firestore for UID:", uid); const userProfile: UserProfile = { uid: uid, email: profileData.email || "", fullName: profileData.fullName, phoneNumber: profileData.phoneNumber || "", address: profileData.address || "", pin: profileData.pin || "", signupType: profileData.signupType || "phone", photoUrl: profileData.photoUrl || "", createdAt: FieldValue.serverTimestamp(), updatedAt: FieldValue.serverTimestamp(), }; const userRef = doc("users", uid); await userRef.set(userProfile); console.log("User profile created successfully in Firestore"); try { await useUserProfileStore.getState().invalidateProfile(uid); } catch (cacheError) { console.warn( "Failed to refresh user profile cache after creation", cacheError ); } } catch (error) { console.error("Error creating user profile:", error); throw error; } }); } // Agent Sign Up with Email/Password static async signUpAgent( email: string, password: string ): Promise<{ user: any | null; error?: string }> { try { const result = await firebaseCreateUserWithEmailAndPassword( email, password ); if (result.error) { return { user: null, error: result.error }; } if (!result.user) { return { user: null, error: "Failed to create user account" }; } // Check if agent exists in agents collection const agentDocRef = doc("agents", result.user.uid); const agentDoc = await agentDocRef.get(); const agentExists = agentDoc.exists; if (!agentExists) { // If agent doesn't exist, create agent document await agentDocRef.set({ uid: result.user.uid, email: email, createdAt: FieldValue.serverTimestamp(), updatedAt: FieldValue.serverTimestamp(), }); } console.log("Agent signup successful, UID:", result.user.uid); return { user: result.user }; } catch (error: any) { console.error("Agent signup error:", error); return { user: null, error: error.message || "Failed to sign up agent", }; } } // Agent Sign In with Email/Password static async signInAgent( email: string, password: string ): Promise<{ user: any | null; error?: string }> { try { console.log("Agent signin attempt for email:", email); const result = await firebaseSignInWithEmailAndPassword(email.trim(), password); if (result.error) { console.error("Firebase signin error:", result.error); // Map common Firebase errors to user-friendly messages let errorMessage = result.error; if (result.error.includes("auth/wrong-password") || result.error.includes("auth/invalid-credential")) { errorMessage = "Invalid email or password"; } else if (result.error.includes("auth/user-not-found")) { errorMessage = "No account found with this email"; } else if (result.error.includes("auth/invalid-email")) { errorMessage = "Invalid email address"; } else if (result.error.includes("auth/too-many-requests")) { errorMessage = "Too many failed attempts. Please try again later"; } return { user: null, error: errorMessage }; } if (!result.user) { console.error("No user returned from Firebase signin"); return { user: null, error: "Failed to sign in" }; } console.log("Firebase signin successful, UID:", result.user.uid); // Check if agent exists in agents collection const agentDocRef = doc("agents", result.user.uid); const agentDoc = await agentDocRef.get(); const agentExists = typeof agentDoc.exists === "function" ? agentDoc.exists() : !!agentDoc.exists; if (!agentExists) { console.log("Agent document not found, creating it automatically"); // Create agent document if it doesn't exist try { await agentDocRef.set({ uid: result.user.uid, email: email.trim(), createdAt: FieldValue.serverTimestamp(), updatedAt: FieldValue.serverTimestamp(), }); console.log("Agent document created successfully"); } catch (createError) { console.error("Error creating agent document:", createError); // Still allow signin even if document creation fails } } else { console.log("Agent document exists"); } console.log("Agent signin successful, UID:", result.user.uid); return { user: result.user }; } catch (error: any) { console.error("Agent signin error:", error); let errorMessage = error.message || "Failed to sign in agent"; // Map common Firebase errors if (errorMessage.includes("auth/wrong-password") || errorMessage.includes("auth/invalid-credential")) { errorMessage = "Invalid email or password"; } else if (errorMessage.includes("auth/user-not-found")) { errorMessage = "No account found with this email"; } return { user: null, error: errorMessage, }; } } // Check if agent exists in agents collection static async checkAgentExists(uid: string): Promise { try { const agentDocRef = doc("agents", uid); const agentDoc = await agentDocRef.get(); const existsValue = agentDoc.exists; return typeof existsValue === "function" ? existsValue() : !!existsValue; } catch (error) { console.error("Error checking agent:", error); return false; } } } export default AuthService;