diff --git a/DevLog/Data/Common/DataLayerError.swift b/DevLog/Data/Common/DataLayerError.swift index c12d40d0..38467ee2 100644 --- a/DevLog/Data/Common/DataLayerError.swift +++ b/DevLog/Data/Common/DataLayerError.swift @@ -10,6 +10,9 @@ import Foundation enum AuthError: Error { case notAuthenticated case failedToUnlinkLastProvider + case linkEmailNotFound + case linkEmailMismatch + case linkCredentialAlreadyInUse case unsupportedProvider } diff --git a/DevLog/Data/Repository/AuthDataRepositoryImpl.swift b/DevLog/Data/Repository/AuthDataRepositoryImpl.swift index 47eb308d..53921aac 100644 --- a/DevLog/Data/Repository/AuthDataRepositoryImpl.swift +++ b/DevLog/Data/Repository/AuthDataRepositoryImpl.swift @@ -53,8 +53,12 @@ final class AuthDataRepositoryImpl: AuthDataRepository { case .github: service = githubAuthService } - - try await service.link(uid: uid, email: email) + + do { + try await service.link(uid: uid, email: email) + } catch { + throw mapLinkError(error) + } } func unlinkProvider(_ provider: AuthProvider) async throws { @@ -80,3 +84,26 @@ final class AuthDataRepositoryImpl: AuthDataRepository { try await service.unlink(uid) } } + +private extension AuthDataRepositoryImpl { + func mapLinkError(_ error: Error) -> Error { + if let emailFetchError = error as? EmailFetchError { + switch emailFetchError { + case .emailNotFound: + return AuthError.linkEmailNotFound + case .emailMismatch: + return AuthError.linkEmailMismatch + } + } + + let nsError = error as NSError + if nsError.domain == AuthErrorDomain, + let authErrorCode = AuthErrorCode(rawValue: nsError.code) { + if authErrorCode == .credentialAlreadyInUse { + return AuthError.linkCredentialAlreadyInUse + } + } + + return error + } +} diff --git a/DevLog/Presentation/ViewModel/AccountViewModel.swift b/DevLog/Presentation/ViewModel/AccountViewModel.swift index 8d80560f..2914bffa 100644 --- a/DevLog/Presentation/ViewModel/AccountViewModel.swift +++ b/DevLog/Presentation/ViewModel/AccountViewModel.swift @@ -40,6 +40,9 @@ final class AccountViewModel: Store { } enum AlertType { + case linkEmailNotFound + case linkEmailMismatch + case linkCredentialAlreadyInUse case error } @@ -114,7 +117,8 @@ final class AccountViewModel: Store { let (currentProvider, allProviders) = try await fetchProvidersUseCase.execute() send(.updateProviders(currentProvider: currentProvider, allProviders: allProviders)) } catch { - send(.setAlert(isPresented: true, type: .error)) + if error.isSocialLoginCancelled { return } + send(.setAlert(isPresented: true, type: linkAlertType(for: error))) } } case .unlink(let provider): @@ -137,8 +141,34 @@ final class AccountViewModel: Store { } private extension AccountViewModel { + func linkAlertType(for error: Error) -> AlertType { + guard let authError = error as? AuthError else { + return .error + } + + switch authError { + case .linkEmailNotFound: + return .linkEmailNotFound + case .linkEmailMismatch: + return .linkEmailMismatch + case .linkCredentialAlreadyInUse: + return .linkCredentialAlreadyInUse + case .notAuthenticated, .failedToUnlinkLastProvider, .unsupportedProvider: + return .error + } + } + func setAlert(_ state: inout State, isPresented: Bool, type: AlertType?) { switch type { + case .linkEmailNotFound: + state.alertTitle = "이메일 확인 불가" + state.alertMessage = "선택한 계정의 이메일 정보를 확인할 수 없어 연결할 수 없어요. 계정 설정을 확인한 뒤 다시 시도해주세요." + case .linkEmailMismatch: + state.alertTitle = "연결할 수 없음" + state.alertMessage = "현재 로그인한 계정과 선택한 계정의 이메일이 달라 연결할 수 없어요. 같은 이메일의 계정으로 다시 시도해주세요." + case .linkCredentialAlreadyInUse: + state.alertTitle = "이미 연결된 계정" + state.alertMessage = "선택한 계정은 이미 다른 계정에 연결되어 있어요. 해당 계정으로 로그인한 뒤 이용해주세요." case .error: state.alertTitle = "오류" state.alertMessage = "문제가 발생했습니다. 잠시 후 다시 시도해주세요."