import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '../stores'; import { useSEO } from '../hooks/useSEO'; import { getAuth, GoogleAuthProvider, signInWithPopup, sendSignInLinkToEmail, isSignInWithEmailLink, signInWithEmailLink } from 'firebase/auth'; import { getFirestore, doc, setDoc, getDoc } from 'firebase/firestore'; const Auth: React.FC = () => { const navigate = useNavigate(); const { isLoading } = useAuthStore(); // SEO optimization for Auth page useSEO({ title: 'Sign In to AiFigure - Create AI Action Figures | Free AI Art Generator', description: 'Sign in to AiFigure to start creating amazing AI-generated action figures. Join our community of artists and generate unique action figures for free using advanced AI technology.', keywords: 'sign in AiFigure, login AI art generator, create account action figures, free AI art platform, AI collectibles community, custom figurine creator login', ogTitle: 'Sign In to AiFigure - Create AI Action Figures | Free AI Art Generator', ogDescription: 'Sign in to AiFigure to start creating amazing AI-generated action figures. Join our community of artists and generate unique action figures for free.', ogImage: 'https://aifigure.com/auth-og-image.jpg', ogUrl: window.location.href, twitterTitle: 'Sign In to AiFigure - Create AI Action Figures | Free AI Art Generator', twitterDescription: 'Sign in to AiFigure to start creating amazing AI-generated action figures. Join our community of artists and generate unique action figures for free.', twitterImage: 'https://aifigure.com/auth-og-image.jpg', canonical: window.location.href, type: 'website', }); // Passwordless email states const [passwordlessEmail, setPasswordlessEmail] = useState(''); // Local states for success and error messages const [success, setSuccess] = useState(''); const [localError, setLocalError] = useState(''); // Helper functions for clearing messages const clearLocalError = () => setLocalError(''); const clearSuccess = () => setSuccess(''); // Handle passwordless email link sign-in React.useEffect(() => { const handleEmailLinkSignIn = async () => { const auth = getAuth(); if (isSignInWithEmailLink(auth, window.location.href)) { console.log('๐ Email link detected, processing sign-in...'); let email = window.localStorage.getItem('emailForSignIn'); // If email not found in localStorage, prompt user if (!email) { console.log('๐ง Email not found in localStorage, prompting user...'); email = window.prompt('Please type your email again'); if (!email) { console.error('โ No email provided by user'); setLocalError('Email address is required to complete sign-in'); return; } } try { console.log('๐ Signing in with email link...'); const result = await signInWithEmailLink(auth, email, window.location.href); console.log('โ Email link sign-in successful!'); // Clean up localStorage window.localStorage.removeItem('emailForSignIn'); // Try to create/update user document, but don't fail auth if it doesn't work try { console.log('๐ Creating/updating user document for email link auth...'); await createUserDocument(result.user, 'passwordless'); console.log('โ User document created/updated successfully'); // Add a small delay to ensure Firestore operation completes setTimeout(() => { console.log('๐ Email link auth completed, navigating to home...'); navigate('/'); }, 1000); } catch (docError) { console.warn('โ ๏ธ User document creation/update failed, but authentication succeeded:', docError); // Still navigate even if document creation fails setTimeout(() => { console.log('๐ Email link auth completed (with doc error), navigating to home...'); navigate('/'); }, 1000); } setSuccess('Successfully signed in! ๐'); } catch (error: any) { console.error('โ Email link sign-in error:', error); // Handle specific Firebase Auth errors let errorMessage = 'Failed to sign in with email link'; if (error.code === 'auth/invalid-action-code') { errorMessage = 'The sign-in link is invalid or has expired.'; } else if (error.code === 'auth/expired-action-code') { errorMessage = 'The sign-in link has expired. Please request a new one.'; } else if (error.code === 'auth/user-disabled') { errorMessage = 'This account has been disabled.'; } else if (error.code === 'auth/invalid-email') { errorMessage = 'Invalid email address.'; } else if (error.message) { errorMessage = error.message; } setLocalError(errorMessage); } } }; handleEmailLinkSignIn(); }, [navigate]); const testFirestoreConnection = async () => { console.log('๐งช Testing Firestore connection...'); try { // Try to read a non-existent document to test permissions console.log('๐ Testing read permission...'); // This should fail gracefully if permissions are correct return true; } catch (error: any) { console.error('โ Firestore connection test failed:', error.code, error.message); return false; } }; /** * Creates or updates a user document in Firestore with comprehensive user information * This function stores all user data including authentication method, tokens, and metadata * CRITICAL: Preserves existing user data (tokens, generatedImages) when user logs in again */ const createUserDocument = async (user: any, method: string = 'unknown', retryCount = 0) => { console.log(`๐ createUserDocument called with method: ${method}, retry: ${retryCount}`); console.log('๐ค Firebase User object:', { uid: user.uid, email: user.email, phoneNumber: user.phoneNumber, displayName: user.displayName, emailVerified: user.emailVerified, isAnonymous: user.isAnonymous }); const db = getFirestore(); const maxRetries = 3; console.log('๐ง Firestore instance:', !!db); // Test Firestore connection first const isConnected = await testFirestoreConnection(); if (!isConnected) { console.error('โ Firestore connection test failed, aborting document creation'); return; } try { console.log('๐ Checking if user document already exists...'); const userDocRef = doc(db, 'users', user.uid); const existingDoc = await getDoc(userDocRef); if (existingDoc.exists()) { console.log('โ User document already exists, updating login metadata only...'); const existingData = existingDoc.data(); // Preserve existing data, only update metadata const updateData = { // Update basic user information (these might change) email: user.email || existingData?.email || null, phoneNumber: user.phoneNumber || existingData?.phoneNumber || null, displayName: user.displayName || existingData?.displayName || null, photoURL: user.photoURL || existingData?.photoURL || null, // Update login metadata lastLogin: new Date(), authMethod: method, // Update auth method in case they switched // Update status emailVerified: user.emailVerified || existingData?.emailVerified || false, isAnonymous: user.isAnonymous || existingData?.isAnonymous || false, // PRESERVE existing application data - DO NOT OVERWRITE // tokens: existingData?.tokens || 25, // Keep existing tokens // generatedImages: existingData?.generatedImages || [], // Keep existing images // totalGenerations: existingData?.totalGenerations || 0, // Keep existing count // Update additional metadata userAgent: navigator.userAgent, }; console.log('๐ Updating existing user with preserved data:', { uid: user.uid, email: updateData.email, preservedTokens: existingData?.tokens, preservedImagesCount: existingData?.generatedImages?.length || 0, lastLogin: updateData.lastLogin }); await setDoc(userDocRef, updateData, { merge: true }); console.log('โ Existing user document updated successfully (data preserved)'); } else { console.log('๐ User document does not exist, creating new user profile...'); // Create new user profile with default values const newUserData = { // Basic user information uid: user.uid, email: user.email || null, phoneNumber: user.phoneNumber || null, displayName: user.displayName || null, photoURL: user.photoURL || null, // Account metadata createdAt: new Date(), lastLogin: new Date(), authMethod: method, // User status and preferences emailVerified: user.emailVerified || false, isAnonymous: user.isAnonymous || false, // Application-specific data - DEFAULT VALUES FOR NEW USERS tokens: 10, // Give new users 10 free tokens subscriptionTier: 'free', generatedImages: [], totalGenerations: 0, accountStatus: 'active', // Additional metadata userAgent: navigator.userAgent, signupSource: 'web_app', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }; console.log('๐ Creating new user with default data:', { uid: newUserData.uid, email: newUserData.email, tokens: newUserData.tokens, createdAt: newUserData.createdAt }); await setDoc(userDocRef, newUserData); console.log('โ New user document created successfully'); } // Verify the document exists and log final state console.log('๐ Verifying final document state...'); const finalDoc = await getDoc(userDocRef); if (finalDoc.exists()) { const finalData = finalDoc.data(); console.log('โ Final document state:', { uid: finalData?.uid, email: finalData?.email, tokens: finalData?.tokens, generatedImagesCount: finalData?.generatedImages?.length || 0, authMethod: finalData?.authMethod, lastLogin: finalData?.lastLogin?.toDate?.()?.toISOString() || finalData?.lastLogin }); } else { console.error('โ Document verification failed - document not found!'); } } catch (error: any) { console.error('โ Error in createUserDocument:', error); console.error('Error details:', { code: error.code, message: error.message, stack: error.stack, name: error.name }); // Specific handling for different error types if (error.code === 'permission-denied') { console.error('๐ซ PERMISSION DENIED: Check Firestore security rules'); console.error('๐ Current user permissions may not allow writes to /users collection'); } else if (error.code === 'unavailable') { console.error('๐ NETWORK UNAVAILABLE: Check internet connection'); console.error('๐ก Firestore service may be temporarily down'); } else if (error.code === 'failed-precondition') { console.error('โ ๏ธ FAILED PRECONDITION: Firestore may not be enabled'); console.error('๐ง Check Firebase Console - enable Firestore Database'); } else if (error.code === 'resource-exhausted') { console.error('โฑ๏ธ RESOURCE EXHAUSTED: Quota exceeded'); console.error('๐ฐ Check Firebase billing/quota limits'); } else { console.error('โ UNKNOWN ERROR: Unexpected Firestore error'); } // Handle retry logic based on error type if (error.code === 'unavailable' || error.code === 'failed-precondition') { console.log('โ ๏ธ Firestore temporarily unavailable, but user auth succeeded'); // Retry document creation if we haven't exceeded max retries if (retryCount < maxRetries) { console.log(`๐ Retrying user document creation (attempt ${retryCount + 1}/${maxRetries})`); setTimeout(() => { createUserDocument(user, method, retryCount + 1); }, 2000 * (retryCount + 1)); // Exponential backoff } else { console.log('โ Max retries exceeded, giving up on document creation'); } } else { console.error('โ Unexpected error in createUserDocument:', error); // Don't throw error for document creation failures during auth } } }; const handleGoogleSignIn = async () => { console.log('๐ Starting Google sign-in process...'); clearLocalError(); setSuccess(''); try { console.log('๐ง Getting Firebase auth instance...'); const auth = getAuth(); console.log('โ Auth instance obtained:', !!auth); const provider = new GoogleAuthProvider(); provider.setCustomParameters({ prompt: 'select_account' }); console.log('๐ง Google provider configured'); console.log('๐ Opening Google sign-in popup...'); const result = await signInWithPopup(auth, provider); console.log('โ Google sign-in successful!'); console.log('๐ค User data:', { uid: result.user.uid, email: result.user.email, displayName: result.user.displayName, phoneNumber: result.user.phoneNumber }); // Try to create/update user document, but don't fail auth if it doesn't work console.log('๐ Attempting to create/update user document...'); try { await createUserDocument(result.user, 'google'); console.log('โ User document created/updated successfully'); } catch (docError: any) { console.warn('โ ๏ธ User document creation/update failed, but authentication succeeded:', docError); console.warn('Error details:', { code: docError?.code, message: docError?.message, stack: docError?.stack }); } console.log('๐ Google sign-in completed, auth store will handle the rest...'); setSuccess('Successfully signed in with Google!'); setTimeout(() => navigate('/'), 1000); console.log('๐ Google sign-in process completed successfully!'); } catch (err: any) { console.error('โ Google sign-in error:', err); console.error('Error details:', { code: err.code, message: err.message, stack: err.stack, customData: err.customData }); // Error will be shown by the store } }; const handlePasswordlessEmail = async (e: React.FormEvent) => { e.preventDefault(); // Validate email format if (!passwordlessEmail || !passwordlessEmail.includes('@')) { setLocalError('Please enter a valid email address'); return; } // Check if we're in production or development const isProduction = window.location.hostname !== 'localhost' && !window.location.hostname.includes('127.0.0.1'); if (!isProduction) { setLocalError('โ ๏ธ Magic link emails are only available in production. For testing, please deploy your app.'); return; } clearLocalError(); clearSuccess(); try { console.log('๐ง Sending magic link to:', passwordlessEmail); const auth = getAuth(); const actionCodeSettings = { url: `${window.location.origin}/auth`, // Redirect to auth page after clicking link handleCodeInApp: true, }; console.log('๐ Sending sign-in link...'); await sendSignInLinkToEmail(auth, passwordlessEmail, actionCodeSettings); // Store email in localStorage for later use window.localStorage.setItem('emailForSignIn', passwordlessEmail); console.log('โ Magic link sent successfully'); setSuccess('Check your email for the verification link! Check your spam folder if you don\'t see it.'); setPasswordlessEmail(''); } catch (err: any) { console.error('โ Magic link error:', err); // Handle specific Firebase Auth errors let errorMessage = 'Failed to send magic link'; if (err.code === 'auth/invalid-email') { errorMessage = 'Invalid email address. Please check and try again.'; } else if (err.code === 'auth/missing-email') { errorMessage = 'Email address is required.'; } else if (err.code === 'auth/user-disabled') { errorMessage = 'This account has been disabled.'; } else if (err.code === 'auth/too-many-requests') { errorMessage = 'Too many requests. Please wait before trying again.'; } else if (err.message) { errorMessage = err.message; } setLocalError(errorMessage); } }; return (
Choose your preferred sign-in method