diff --git a/lib/services/auth/auth_service.dart b/lib/services/auth/auth_service.dart index 632805f..e118b7a 100644 --- a/lib/services/auth/auth_service.dart +++ b/lib/services/auth/auth_service.dart @@ -131,6 +131,16 @@ class FirebaseAuthService implements AuthService { ); } + bool get _hasAnonymousFirebaseUser => + _firebaseAuth?.currentUser?.isAnonymous == true; + + void _updateAuthenticatedUser(fb.User user) { + _updateState(AuthState.authenticated( + _mapFirebaseUser(user), + isFirebaseConfigured: true, + )); + } + @override Future signInWithEmailAndPassword(String email, String password) async { final auth = _firebaseAuth; @@ -149,13 +159,29 @@ class FirebaseAuthService implements AuthService { )); try { + final normalizedEmail = email.trim(); + + // If the current Firebase identity is anonymous, upgrade that identity + // instead of signing into a new Firebase UID. This preserves ownership + // of any cloud data already associated with the guest account. + if (_hasAnonymousFirebaseUser) { + final credential = fb.EmailAuthProvider.credential( + email: normalizedEmail, + password: password, + ); + final result = await auth.currentUser!.linkWithCredential(credential); + if (result.user != null) { + _updateAuthenticatedUser(result.user!); + } + return; + } + final credential = await auth.signInWithEmailAndPassword( - email: email.trim(), + email: normalizedEmail, password: password, ); if (credential.user != null) { - final user = _mapFirebaseUser(credential.user!); - _updateState(AuthState.authenticated(user, isFirebaseConfigured: true)); + _updateAuthenticatedUser(credential.user!); } } catch (e) { _updateState(AuthState.error( @@ -188,16 +214,29 @@ class FirebaseAuthService implements AuthService { )); try { - final credential = await auth.createUserWithEmailAndPassword( - email: email.trim(), + final normalizedEmail = email.trim(); + final emailCredential = fb.EmailAuthProvider.credential( + email: normalizedEmail, password: password, ); + + fb.UserCredential credential; + if (_hasAnonymousFirebaseUser) { + // Upgrade the existing anonymous UID rather than creating a second + // account. This keeps prior guest-owned cloud data attached to the user. + credential = await auth.currentUser!.linkWithCredential(emailCredential); + } else { + credential = await auth.createUserWithEmailAndPassword( + email: normalizedEmail, + password: password, + ); + } + if (credential.user != null) { if (displayName != null && displayName.trim().isNotEmpty) { await credential.user!.updateDisplayName(displayName.trim()); } - final user = _mapFirebaseUser(credential.user!); - _updateState(AuthState.authenticated(user, isFirebaseConfigured: true)); + _updateAuthenticatedUser(credential.user!); } } catch (e) { _updateState(AuthState.error( @@ -231,7 +270,14 @@ class FirebaseAuthService implements AuthService { if (googleUser == null) { // User cancelled the sign-in modal/flow - _updateState(_currentState.copyWith(status: _currentState.user != null ? AuthStatus.authenticated : AuthStatus.unauthenticated, clearError: true)); + _updateState(_currentState.copyWith( + status: _currentState.user != null + ? (_currentState.user!.isAnonymous + ? AuthStatus.anonymous + : AuthStatus.authenticated) + : AuthStatus.unauthenticated, + clearError: true, + )); return; } @@ -241,10 +287,17 @@ class FirebaseAuthService implements AuthService { idToken: googleAuth.idToken, ); - final result = await auth.signInWithCredential(credential); + fb.UserCredential result; + if (_hasAnonymousFirebaseUser) { + // Critical: link the Google provider to the existing anonymous user. + // Do not call signInWithCredential here or the guest UID can be replaced. + result = await auth.currentUser!.linkWithCredential(credential); + } else { + result = await auth.signInWithCredential(credential); + } + if (result.user != null) { - final user = _mapFirebaseUser(result.user!); - _updateState(AuthState.authenticated(user, isFirebaseConfigured: true)); + _updateAuthenticatedUser(result.user!); } } catch (e) { _updateState(AuthState.error( @@ -358,7 +411,11 @@ class FirebaseAuthService implements AuthService { case 'invalid-credential': return 'Invalid credentials. Please verify your email and password.'; case 'email-already-in-use': - return 'An account already exists with this email address.'; + return 'An account already exists with this email address. Sign in to that account instead.'; + case 'credential-already-in-use': + return 'This Google account is already linked to another MotorSignal account. Sign in with that account instead of replacing the current guest account.'; + case 'provider-already-linked': + return 'This sign-in provider is already linked to the current account.'; case 'invalid-email': return 'The email address format is invalid.'; case 'weak-password':