Amba-Agent-App/lib/services/authServices.ts
2026-01-16 00:22:35 +03:00

458 lines
14 KiB
TypeScript

/**
* 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<GoogleSignInResult> {
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<void> {
try {
await firebaseSignOutFromGoogle();
} catch (error) {
console.error("Google Sign-Out error:", error);
}
}
// Phone number authentication - send OTP
static async sendOTP(phoneNumber: string): Promise<PhoneAuthResult> {
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<OTPVerificationResult> {
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<boolean> {
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<void> {
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<boolean> {
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;