Skip to content

[FEAT] 로그인 구현#12

Merged
Sangyoon98 merged 3 commits intodevfrom
SPM-20
Oct 16, 2025
Merged

[FEAT] 로그인 구현#12
Sangyoon98 merged 3 commits intodevfrom
SPM-20

Conversation

@Sangyoon98
Copy link
Copy Markdown
Member

@Sangyoon98 Sangyoon98 commented Oct 15, 2025

📝 Summary

로그인 기능 구현

🙏 Question & PR point

📬 Reference

Summary by CodeRabbit

  • New Features

    • 로그인·회원가입 화면 추가 및 인증 흐름 도입(자동 로그인 확인, 성공 시 진입).
    • 토큰 기반 인증 저장·관리(Keychain 연동) 및 인증 API 연동 추가.
    • 로그인/회원가입 폼과 유효성 검사, 필드별 오류 표시, 뷰모델 기반 상태 관리.
  • Style

    • 앱 전역 폰트를 Gmarket Sans로 적용.
    • 버튼 및 입력 필드 시각 개선(모서리, 색상, 오류 테두리 등).
    • 라이트/다크 모드 대응 로고 및 보조 색상 자산 추가.
  • Chores

    • API 기본 주소를 운영 도메인으로 업데이트.
    • 폰트 리소스 등록 및 에셋 추가.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 15, 2025

Walkthrough

Swinject 기반 싱글톤 DI를 제거하고 AppDependencies로 의존성 그래프를 전환했으며, RootView 기반 인증 흐름과 Auth 도메인(DTO/API/Repository/UseCase/UI)을 추가했습니다. 네트워크 기본 URL, 커스텀 폰트·색상·이미지 자산, 공통 UI 컴포넌트 변경 및 Toast Swift 패키지 참조가 통합되었습니다.

Changes

Cohort / File(s) Summary
Project Integration
SampoomManagement.xcodeproj/project.pbxproj, SampoomManagement/Info.plist
Toast Swift 패키지(XCRemoteSwiftPackageReference 및 product dependency) 추가, Frameworks 빌드파일에 Toast 포함. 여러 빌드 설정에 INFOPLIST_FILE 명시. Info.plist에 GmarketSans 폰트 3종 등록( UIAppFonts ).
App Entry & Root Flow
SampoomManagement/App/SampoomManagementApp.swift, SampoomManagement/App/RootView.swift, SampoomManagement/App/ContentView.swift
App에 AppDependencies 인스턴스 추가 및 주입. 진입점이 RootView(dependencies:)로 변경되고 인증 흐름(Login/SignUp) 추가. ContentView가 DIContainer 대신 AppDependencies로 ViewModel 생성.
DI Refactor
SampoomManagement/Core/DI/AppDependencies.swift, SampoomManagement/Core/DI/DIContainer.swift (삭제), SampoomManagement/Core/DI/CoreDIModule.swift (삭제), SampoomManagement/Features/Part/DI/PartDIModule.swift (삭제)
기존 Swinject 기반 모듈 및 전역 DIContainer 제거. AppDependencies로 네트워크/레포지토리/유스케이스/뷰모델 팩토리 제공.
Auth Feature
Features/Auth/** (많은 파일, 예: Data/Remote/API/AuthAPI.swift, Data/Remote/DTO/*, Data/Repository/AuthRepositoryImpl.swift, Data/Local/Preferences/AuthPreferences.swift, Data/Local/Preferences/KeychainManager.swift, Domain/*, UI/*)
인증 전체 스택 추가: DTOs, API, Mapper, AuthRepositoryImpl, Keychain 기반 토큰 관리(AuthPreferences/KeychainManager), Login/SignUp UseCase, ViewModel, View, UI 상태·이벤트, 유효성 검사, 문자열 리소스 확장 등.
Networking
SampoomManagement/Core/Network/NetworkManager.swift, SampoomManagement/Core/Network/NetworkError.swift
baseURL을 https://sampoom.store/api/로 변경. 인증 관련 에러 타입 AuthError 추가(토큰 저장 실패 등).
UI Components & Utilities
SampoomManagement/Core/UI/Components/CommonButton.swift, .../CommonTextField.swift, .../Font+Extension.swift, .../KeyboardObserver.swift
CommonButton 스타일 및 색상/레이아웃 변경. CommonTextField가 외부 바인딩·포커스·에러 상태를 수용하도록 리팩터링. GmarketSans 폰트 확장 추가. 키보드 높이 옵저버(KeyboardObserver) 추가.
Resources & Assets
SampoomManagement/Core/Resources/StringResources.swift, SampoomManagement/Resources/Assets.xcassets/*
인증 관련 문자열 대거 추가(StringResources.Auth). Disable/TextSecondary 컬러셋 및 로고 이미지 에셋(oneline_logo, square_logo) 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as SampoomManagementApp
  participant Dep as AppDependencies
  participant Root as RootView
  participant Pref as AuthPreferences
  participant LVm as LoginViewModel
  participant SVm as SignUpViewModel
  participant LUC as LoginUseCase
  participant SUC as SignUpUseCase
  participant Repo as AuthRepositoryImpl
  participant API as AuthAPI
  participant Net as NetworkManager

  App->>Dep: init AppDependencies()
  App->>Root: RootView(dependencies: Dep)
  Root->>Pref: hasToken()
  alt 토큰 존재
    Root-->User: Show ContentView
  else 토큰 없음
    Root-->User: Show LoginView
  end

  User->>LVm: submit(email,password)
  LVm->>LUC: execute(email,password)
  LUC->>Repo: signIn(...)
  Repo->>API: login(...)
  API->>Net: request("auth/login")
  Net-->>API: response(LoginResponseDTO)
  API-->>Repo: LoginResponseDTO
  Repo->>Pref: saveToken(access, refresh)
  Repo-->>LUC: User
  LUC-->>LVm: User
  LVm-->>Root: success -> Show ContentView
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60-90 minutes

Possibly related PRs

Suggested labels

ready-to-merge

Suggested reviewers

  • Lee-Jong-Jin
  • yangjiseonn
  • vivivim
  • taemin3

Poem

당근 밭에서 폰트 꺼내온 토끼예요 🐇
의존성 실타래 풀고 새 길 만들었죠.
로그인 톡, 회원가입 휙—토스트 한입 🎉
키보드 높아도 폼은 찰칵, 에셋은 반짝,
숲속 빌드가 오늘도 무사히 완성!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목 “[FEAT] 로그인 구현”은 PR에서 실제로 구현된 로그인 기능을 간결하게 요약하고 있어 주요 변경 내용을 빠르게 파악할 수 있습니다. 비록 이외에도 DI 리팩토링이나 회원가입 흐름 등이 포함되어 있지만, 로그인 구현은 PR의 핵심 목표로 보이므로 적절합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SPM-20

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2e86c7 and 54e4841.

📒 Files selected for processing (2)
  • SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (3)
SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (2)
  • signup (45-86)
  • login (19-42)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (5)
  • saveToken (18-28)
  • clear (57-65)
  • hasToken (38-48)
  • getAccessToken (30-32)
  • getRefreshToken (34-36)
SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1)
  • toModel (11-20)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (2)
SampoomManagement/Features/Auth/Data/Local/Preferences/KeychainManager.swift (3)
  • save (24-43)
  • delete (73-85)
  • get (45-71)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (2)
  • getAccessToken (68-70)
  • getRefreshToken (72-74)
🔇 Additional comments (10)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (5)

18-28: 이전 리뷰 피드백이 잘 반영되었습니다.

saveToken 메서드가 이제 부분 저장 실패 시 롤백 메커니즘을 구현하고 에러를 적절히 전파합니다. 다만, 롤백 시 try?를 사용하여 삭제 실패를 무시하는데, 이는 롤백 과정에서 발생하는 에러가 원본 에러보다 덜 중요하므로 적절한 처리입니다.


30-36: 이전 리뷰 피드백이 잘 반영되었습니다.

getAccessTokengetRefreshToken 메서드가 이제 에러를 적절히 전파하여 호출자가 "토큰 없음"과 "키체인 접근 실패"를 구분할 수 있습니다.


38-48: 키체인 에러를 로깅하면서도 편의성을 제공하는 좋은 설계입니다.

hasToken 메서드는 키체인 접근 오류를 로깅한 후 false를 반환하여, 호출자가 에러 처리를 하지 않아도 되는 편의 메서드로 동작합니다. 에러를 전파해야 하는 경우를 위해 hasTokenSafely도 제공되어 있어 유연성이 확보됩니다.


51-55: throwing 버전 제공으로 유연성 확보.

에러 처리가 필요한 상황을 위한 hasTokenSafely 메서드 추가가 좋습니다.


57-65: 로그아웃 컨텍스트에 적합한 에러 처리.

clear 메서드는 로그아웃 시나리오에서 삭제 실패를 무시하는 것이 적절합니다. 에러를 로깅하여 디버깅 가능성도 확보되어 있습니다.

SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (5)

14-17: 의존성 주입 설계가 올바릅니다.

생성자를 통한 의존성 주입으로 테스트 가능성과 유지보수성이 확보되었습니다.


20-39: 회원가입 후 자동 로그인 흐름을 확인해주세요.

회원가입 응답을 폐기하고 자동으로 로그인을 시도합니다. 만약 회원가입은 성공했지만 자동 로그인이 실패하면, 계정은 생성되었으나 사용자는 로그아웃 상태가 됩니다.

이것이 의도된 동작인지 확인해주세요:

  • 자동 로그인 실패 시 사용자에게 수동 로그인을 안내하는 것이 적절한가요?
  • 아니면 회원가입 응답의 토큰을 직접 사용하여 자동 로그인 실패 가능성을 제거해야 하나요?

41-57: 토큰 저장 실패 시나리오에 대한 처리가 적절합니다.

토큰 저장 실패 시 AuthError.tokenSaveFailed를 throw하여 에러를 명확히 전파합니다. 다만, 이 경우 서버에서는 인증이 성공했지만 로컬에는 토큰이 저장되지 않아 사용자는 로그아웃 상태로 보입니다.

이것이 의도된 동작인지 확인해주세요. 대안으로는:

  • 로그인 성공 후 토큰 저장 실패 시 재시도 로직 추가
  • 사용자에게 명확한 에러 메시지 제공 (예: "로그인은 성공했으나 토큰 저장에 실패했습니다. 다시 시도해주세요.")

59-61: LGTM!

signOut 메서드가 async throws로 선언되어 있지만 실제로는 비동기 작업이나 에러를 throw하지 않습니다. 이는 AuthRepository 프로토콜 요구사항이거나 향후 API 호출을 위한 확장성을 위한 것으로 보입니다.


67-74: LGTM!

토큰 조회 메서드들이 AuthPreferences에 적절히 위임하고 에러를 전파합니다.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (18)
SampoomManagement/Features/Auth/UI/LoginUiState.swift (1)

47-65: 이중 옵셔널 패턴이 올바르게 구현되었습니다.

copy 메서드의 이중 옵셔널(String??) 패턴이 SignUpUiState와 일관되게 사용되어 "값 변경 없음"과 "nil로 설정" 을 구별할 수 있습니다. 구현은 기술적으로 정확하지만, 향후 유지보수성을 위해 더 명시적인 방식(예: 열거형 사용)을 고려해볼 수 있습니다.

SampoomManagement/App/RootView.swift (1)

24-71: 하드코딩된 색상 값을 Color asset으로 교체하세요.

Line 47에서 하드코딩된 RGB 값 Color(red: 0.5, green: 0.2, blue: 0.8)이 사용되고 있습니다. 일관된 디자인 시스템 유지를 위해 Color asset(예: Color(.accent))을 사용하는 것이 좋습니다.

다음과 같이 수정할 수 있습니다:

-                                    .foregroundColor(Color(red: 0.5, green: 0.2, blue: 0.8))
+                                    .foregroundColor(Color(.accent))
SampoomManagement/Features/Auth/UI/LoginViewModel.swift (1)

57-66: 에러 상태 관리를 개선하는 것을 고려하세요.

Line 65의 타임스탬프 추가 방식은 onChange를 강제로 트리거하기 위한 workaround입니다. 이는 동작하지만 코드 냄새가 있으며, 에러 메시지에 타임스탬프가 포함되어 다른 곳에서 파싱이 필요합니다(SignUpView.swift Line 168 참조).

더 깔끔한 대안으로 @Published var errorId = UUID()와 같은 별도의 식별자를 사용하거나, Combine의 removeDuplicates()를 활용하는 방법을 고려하세요.

SampoomManagement/Features/Auth/Domain/Models/User.swift (1)

10-17: 도메인 모델과 인증 토큰의 분리를 고려하세요.

현재 User 모델이 사용자 정보(id, name, role)와 인증 토큰(accessToken, refreshToken, expiresIn)을 함께 포함하고 있습니다.

현재 구조로도 동작하지만, 토큰은 별도의 생명주기와 관리 방식(갱신, 만료 처리)이 필요할 수 있습니다. 향후 확장성을 위해 토큰을 별도의 타입으로 분리하는 것을 고려하세요.

SampoomManagement/Features/Auth/Domain/ValidationResult.swift (1)

10-27: LGTM! 선택적 개선 사항을 고려하세요.

ValidationResult 구현이 명확하고 사용하기 쉽습니다.

선택적 개선 사항:

  1. Swift의 표준 Result<Void, String> 타입 사용을 고려할 수 있습니다.
  2. isSuccess 구현을 더 간결하게 할 수 있습니다:
var isSuccess: Bool {
    switch self {
    case .success: return true
    case .error: return false
    }
}

현재 구현도 충분히 명확하므로 선택사항입니다.

SampoomManagement/Features/Auth/UI/SignUpView.swift (3)

11-24: 사용하지 않는 KeyboardObserver를 제거하세요.

Line 13의 keyboardObserver가 선언되었지만 이 뷰에서 사용되지 않습니다. 불필요한 메모리 할당과 관찰을 방지하기 위해 제거하세요.

-    @StateObject private var keyboardObserver = KeyboardObserver()

25-152: LGTM! 선택적으로 반복 코드 추출을 고려하세요.

폼 구조가 일관되고 명확합니다. 각 필드가 적절한 validation과 error handling을 가지고 있습니다.

선택적 개선: 레이블 + 텍스트 필드 패턴이 반복되므로, 재사용 가능한 헬퍼 View를 추출하면 코드 중복을 줄일 수 있습니다. 하지만 현재 구조도 충분히 명확하고 유지보수 가능합니다.


177-179: 코드 중복을 제거하세요.

hideKeyboard 메서드는 LoginView.swift Line 114-116에도 동일하게 구현되어 있습니다.

공통 extension이나 helper로 추출하여 중복을 제거하세요.

예를 들어 View extension으로 추출할 수 있습니다:

// View+Extension.swift
extension View {
    func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

그 후 뷰에서 .onTapGesture { hideKeyboard() } 형태로 사용할 수 있습니다.

SampoomManagement/Core/UI/Extensions/Font+Extension.swift (1)

93-97: UIFont 초기화 실패 처리를 고려하세요.

UIFont 초기화가 옵셔널을 반환하므로, 폰트가 등록되지 않은 경우 nil이 반환됩니다. 호출하는 쪽에서 적절한 fallback 처리가 필요합니다.

필요시 기본 폰트로 fallback하는 로직을 추가할 수 있습니다:

 extension UIFont {
     static func gmarketSans(size: CGFloat, weight: Font.GmarketSansWeight) -> UIFont? {
-        return UIFont(name: weight.name, size: size)
+        return UIFont(name: weight.name, size: size) ?? .systemFont(ofSize: size)
     }
 }
SampoomManagement/Features/Auth/UI/LoginView.swift (2)

13-13: 사용되지 않는 KeyboardObserver를 제거하세요.

KeyboardObserver가 선언되었지만 사용되지 않고 있습니다(93번 라인의 offset이 주석 처리됨).

키보드 높이 조정이 필요하지 않다면 해당 프로퍼티를 제거하세요:

-    @StateObject private var keyboardObserver = KeyboardObserver()

93-93: 주석 처리된 코드를 정리하세요.

키보드 높이에 따른 offset 조정 코드가 주석 처리되어 있습니다. 향후 사용 계획이 없다면 제거하는 것이 좋습니다.

SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (2)

19-26: 불필요한 중간 DTO 객체 생성 제거 고려

LoginRequestDTO를 생성한 후 즉시 파라미터 딕셔너리로 변환하고 있어 불필요한 객체 할당이 발생합니다. 파라미터를 직접 생성하면 더 간결하고 효율적입니다.

다음 diff를 적용하여 리팩토링할 수 있습니다:

 func login(email: String, password: String) async throws -> APIResponse<LoginResponseDTO> {
     return try await withCheckedThrowingContinuation { continuation in
-        let requestDTO = LoginRequestDTO(email: email, password: password)
-        
         let parameters: [String: Any] = [
-            "email": requestDTO.email,
-            "password": requestDTO.password
+            "email": email,
+            "password": password
         ]

53-70: 불필요한 중간 DTO 객체 생성 제거 고려

SignupRequestDTO를 생성한 후 즉시 파라미터 딕셔너리로 변환하고 있어 불필요한 객체 할당이 발생합니다. 로그인 메서드와 동일한 패턴입니다.

다음 diff를 적용하여 리팩토링할 수 있습니다:

 ) async throws -> APIResponse<SignupResponseDTO> {
     return try await withCheckedThrowingContinuation { continuation in
-        let requestDTO = SignupRequestDTO(
-            userName: userName,
-            workspace: workspace,
-            branch: branch,
-            position: position,
-            email: email,
-            password: password
-        )
-        
         let parameters: [String: Any] = [
-            "email": requestDTO.email,
-            "password": requestDTO.password,
-            "workspace": requestDTO.workspace,
-            "branch": requestDTO.branch,
-            "userName": requestDTO.userName,
-            "position": requestDTO.position
+            "email": email,
+            "password": password,
+            "workspace": workspace,
+            "branch": branch,
+            "userName": userName,
+            "position": position
         ]
SampoomManagement/Features/Auth/Domain/AuthValidator.swift (1)

38-56: 비밀번호 최대 길이 검증 추가 권장

현재 최소 길이(8자)와 영문/숫자 포함 여부만 검증하고 있습니다. 매우 긴 비밀번호는 해싱 시 서버 리소스를 과도하게 사용할 수 있어 DoS 공격 벡터가 될 수 있습니다. 일반적으로 최대 길이(예: 128자)를 설정하는 것이 좋습니다.

다음과 같이 최대 길이 검증을 추가할 수 있습니다:

 static func validatePassword(_ password: String) -> ValidationResult {
     if password.isEmpty {
         return .error(StringResources.Auth.passwordRequired)
     }
     
     if password.count < 8 {
         return .error(StringResources.Auth.passwordTooShort)
     }
+    
+    if password.count > 128 {
+        return .error(StringResources.Auth.passwordTooLong)
+    }
     
     // 영문, 숫자 포함 여부 확인
     let hasLetter = password.rangeOfCharacter(from: .letters) != nil

Note: StringResources.Auth.passwordTooLong 문자열 리소스도 추가해야 합니다.

SampoomManagement/Features/Auth/Data/Local/Preferences/KeychainManager.swift (1)

24-43: 강제 언래핑 대신 안전한 처리 권장

Line 25에서 data(using: .utf8)!의 강제 언래핑을 사용하고 있습니다. Swift의 String은 항상 유효한 UTF-8이므로 실제로는 안전하지만, 강제 언래핑은 코드를 읽는 사람에게 잠재적 크래시 위험을 암시합니다. 명시적으로 안전성을 표현하는 것이 좋습니다.

다음과 같이 guard let을 사용할 수 있습니다:

 func save(_ value: String, for key: String) throws {
-    let data = value.data(using: .utf8)!
+    guard let data = value.data(using: .utf8) else {
+        throw KeychainError.unknown(errSecParam)
+    }
     
     let query: [String: Any] = [
SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (1)

105-108: onChange 트리거를 위한 타임스탬프 사용

동일한 에러 메시지가 연속으로 발생할 때 SwiftUI의 onChange가 트리거되지 않는 문제를 해결하기 위해 타임스탬프를 추가하는 방식을 사용하고 있습니다. 이는 알려진 우회 방법이지만, 에러 메시지에 타임스탬프가 포함되어 문자열이 오염됩니다.

더 깔끔한 대안으로 별도의 에러 ID나 카운터를 사용하는 방법을 고려해보세요. 예를 들어, errorId: UUID?를 추가하고 에러 발생 시마다 새 UUID를 생성하는 방식입니다.

예시:

// UiState에 추가
struct SignUpUiState {
    // ...
    var errorId: UUID?
    var error: String?
}

// ViewModel에서 사용
private func showError(_ message: String) {
    uiState = uiState.copy(error: message, errorId: UUID())
}

// View에서
.onChange(of: viewModel.uiState.errorId) { _, newId in
    if let newId = newId, let error = viewModel.uiState.error {
        // Show toast
    }
}
SampoomManagement/Features/Auth/Domain/UseCase/SignUpUseCase.swift (2)

10-15: 구조는 좋지만 몇 가지 Swift 모범 사례를 적용하면 더 좋습니다.

클래스 구조와 의존성 주입 패턴이 잘 구현되어 있고 LoginUseCase와 일관성 있게 설계되었습니다. 다음 개선 사항을 고려해보세요:

  1. final 키워드 추가 - 상속을 방지하고 성능을 개선합니다
  2. 명시적인 접근 제어자 추가 - 의도를 명확히 합니다

다음 diff를 적용하여 개선할 수 있습니다:

-class SignUpUseCase {
+final class SignUpUseCase {
     private let repository: AuthRepository
     
-    init(repository: AuthRepository) {
+    internal init(repository: AuthRepository) {
         self.repository = repository
     }

17-33: 로직은 정확하나 문서화를 추가하면 좋습니다.

메서드 구현이 올바르고 repository layer로의 위임이 적절합니다. AuthRepositoryImpl의 구현을 보면 회원가입 후 자동 로그인이 수행되는데, 이러한 중요한 동작을 문서화하는 것을 권장합니다.

다음과 같이 문서화 및 접근 제어자를 추가할 수 있습니다:

+    /// 회원가입을 수행하고 자동으로 로그인합니다.
+    /// - Parameters:
+    ///   - userName: 사용자 이름
+    ///   - workspace: 작업 공간
+    ///   - branch: 지점
+    ///   - position: 직책
+    ///   - email: 이메일 주소
+    ///   - password: 비밀번호
+    /// - Returns: 로그인된 사용자 정보
+    /// - Throws: 회원가입 또는 로그인 중 발생한 오류
-    func execute(
+    internal func execute(
         userName: String,
         workspace: String,
         branch: String,
         position: String,
         email: String,
         password: String
     ) async throws -> User {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ae555b and df55ce5.

⛔ Files ignored due to path filters (9)
  • SampoomManagement/Resources/Assets.xcassets/oneline_logo.imageset/oneline_logo 1.svg is excluded by !**/*.svg
  • SampoomManagement/Resources/Assets.xcassets/oneline_logo.imageset/oneline_logo.svg is excluded by !**/*.svg
  • SampoomManagement/Resources/Assets.xcassets/oneline_logo.imageset/oneline_logo_dark.svg is excluded by !**/*.svg
  • SampoomManagement/Resources/Assets.xcassets/square_logo.imageset/square_logo_dark.svg is excluded by !**/*.svg
  • SampoomManagement/Resources/Assets.xcassets/square_logo.imageset/square_logo_light 1.svg is excluded by !**/*.svg
  • SampoomManagement/Resources/Assets.xcassets/square_logo.imageset/square_logo_light.svg is excluded by !**/*.svg
  • SampoomManagement/Resources/Fonts/GmarketSansBold.otf is excluded by !**/*.otf
  • SampoomManagement/Resources/Fonts/GmarketSansLight.otf is excluded by !**/*.otf
  • SampoomManagement/Resources/Fonts/GmarketSansMedium.otf is excluded by !**/*.otf
📒 Files selected for processing (42)
  • SampoomManagement.xcodeproj/project.pbxproj (9 hunks)
  • SampoomManagement/App/ContentView.swift (1 hunks)
  • SampoomManagement/App/RootView.swift (1 hunks)
  • SampoomManagement/App/SampoomManagementApp.swift (1 hunks)
  • SampoomManagement/Core/DI/AppDependencies.swift (1 hunks)
  • SampoomManagement/Core/DI/CoreDIModule.swift (0 hunks)
  • SampoomManagement/Core/DI/DIContainer.swift (0 hunks)
  • SampoomManagement/Core/Network/NetworkManager.swift (1 hunks)
  • SampoomManagement/Core/Resources/StringResources.swift (1 hunks)
  • SampoomManagement/Core/UI/Components/CommonButton.swift (3 hunks)
  • SampoomManagement/Core/UI/Components/CommonTextField.swift (4 hunks)
  • SampoomManagement/Core/UI/Extensions/Font+Extension.swift (1 hunks)
  • SampoomManagement/Core/Utilities/KeyboardObserver.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Local/Preferences/KeychainManager.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/DTO/LoginRequestDTO.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/DTO/LoginResponseDTO.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/DTO/SignupRequestDTO.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Remote/DTO/SignupResponseDTO.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/AuthValidator.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/Models/User.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/Repository/AuthRepository.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/UseCase/LoginUseCase.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/UseCase/SignUpUseCase.swift (1 hunks)
  • SampoomManagement/Features/Auth/Domain/ValidationResult.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/LoginUiEvent.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/LoginUiState.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/LoginView.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/LoginViewModel.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/SignUpUiEvent.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/SignUpUiState.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/SignUpView.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (1 hunks)
  • SampoomManagement/Features/Part/DI/PartDIModule.swift (0 hunks)
  • SampoomManagement/Info.plist (1 hunks)
  • SampoomManagement/Resources/Assets.xcassets/Disable.colorset/Contents.json (1 hunks)
  • SampoomManagement/Resources/Assets.xcassets/TextSecondary.colorset/Contents.json (1 hunks)
  • SampoomManagement/Resources/Assets.xcassets/oneline_logo.imageset/Contents.json (1 hunks)
  • SampoomManagement/Resources/Assets.xcassets/square_logo.imageset/Contents.json (1 hunks)
💤 Files with no reviewable changes (3)
  • SampoomManagement/Features/Part/DI/PartDIModule.swift
  • SampoomManagement/Core/DI/DIContainer.swift
  • SampoomManagement/Core/DI/CoreDIModule.swift
🧰 Additional context used
🧬 Code graph analysis (18)
SampoomManagement/Features/Auth/Domain/UseCase/SignUpUseCase.swift (2)
SampoomManagement/Features/Auth/Domain/UseCase/LoginUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • signUp (20-39)
SampoomManagement/Features/Auth/UI/LoginUiState.swift (1)
SampoomManagement/Features/Auth/UI/SignUpUiState.swift (1)
  • copy (82-118)
SampoomManagement/App/ContentView.swift (1)
SampoomManagement/Core/DI/AppDependencies.swift (1)
  • makePartViewModel (58-60)
SampoomManagement/Features/Auth/UI/SignUpUiState.swift (1)
SampoomManagement/Features/Auth/UI/LoginUiState.swift (1)
  • copy (47-65)
SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (3)
SampoomManagement/Features/Auth/UI/SignUpUiState.swift (1)
  • copy (82-118)
SampoomManagement/Features/Auth/Domain/AuthValidator.swift (4)
  • validateEmail (20-35)
  • validatePassword (38-56)
  • validatePasswordCheck (59-69)
  • validateNotEmpty (12-17)
SampoomManagement/Features/Auth/Domain/UseCase/SignUpUseCase.swift (1)
  • execute (17-33)
SampoomManagement/Features/Auth/UI/LoginUiEvent.swift (2)
SampoomManagement/Features/Auth/UI/LoginViewModel.swift (1)
  • submit (35-55)
SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (1)
  • submit (62-97)
SampoomManagement/App/SampoomManagementApp.swift (1)
SampoomManagement/Core/UI/Extensions/Font+Extension.swift (2)
  • gmarketSans (28-30)
  • gmarketSans (94-96)
SampoomManagement/Features/Auth/Domain/Repository/AuthRepository.swift (1)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (4)
  • signUp (20-39)
  • signIn (41-51)
  • signOut (53-55)
  • isSignedIn (57-59)
SampoomManagement/App/RootView.swift (2)
SampoomManagement/Core/DI/AppDependencies.swift (2)
  • makeLoginViewModel (50-52)
  • makeSignUpViewModel (54-56)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (1)
  • hasToken (31-33)
SampoomManagement/Features/Auth/UI/SignUpView.swift (3)
SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (8)
  • updateName (23-26)
  • updateBranch (29-32)
  • updatePosition (35-38)
  • updateEmail (41-44)
  • updatePassword (47-53)
  • updatePasswordCheck (56-59)
  • submit (62-97)
  • consumeError (100-102)
SampoomManagement/Features/Auth/UI/LoginViewModel.swift (4)
  • updateEmail (23-26)
  • updatePassword (29-32)
  • submit (35-55)
  • consumeError (58-60)
SampoomManagement/Features/Auth/UI/LoginView.swift (1)
  • hideKeyboard (115-117)
SampoomManagement/Features/Auth/UI/LoginView.swift (1)
SampoomManagement/Features/Auth/UI/LoginViewModel.swift (4)
  • updateEmail (23-26)
  • updatePassword (29-32)
  • submit (35-55)
  • consumeError (58-60)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (1)
SampoomManagement/Features/Auth/Data/Local/Preferences/KeychainManager.swift (3)
  • save (24-43)
  • get (45-71)
  • delete (73-85)
SampoomManagement/Features/Auth/UI/SignUpUiEvent.swift (1)
SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (1)
  • submit (62-97)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (3)
SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (2)
  • signup (45-86)
  • login (19-42)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (3)
  • saveToken (18-21)
  • clear (35-38)
  • hasToken (31-33)
SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1)
  • toModel (11-20)
SampoomManagement/Features/Auth/Domain/UseCase/LoginUseCase.swift (2)
SampoomManagement/Features/Auth/Domain/UseCase/SignUpUseCase.swift (1)
  • execute (17-33)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1)
  • signIn (41-51)
SampoomManagement/Features/Auth/UI/LoginViewModel.swift (3)
SampoomManagement/Features/Auth/UI/LoginUiState.swift (1)
  • copy (47-65)
SampoomManagement/Features/Auth/Domain/AuthValidator.swift (1)
  • validateNotEmpty (12-17)
SampoomManagement/Features/Auth/Domain/UseCase/LoginUseCase.swift (1)
  • execute (17-19)
SampoomManagement/Features/Auth/Domain/AuthValidator.swift (3)
SampoomManagement/Core/Resources/StringResources.swift (1)
  • fieldRequired (101-103)
SampoomManagement/Features/Auth/UI/LoginViewModel.swift (2)
  • validateEmail (70-76)
  • validatePassword (78-84)
SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (3)
  • validateEmail (136-139)
  • validatePassword (141-144)
  • validatePasswordCheck (146-152)
SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (1)
SampoomManagement/Core/Network/NetworkManager.swift (1)
  • request (18-50)
🪛 SwiftLint (0.57.0)
SampoomManagement/Core/UI/Components/CommonTextField.swift

[Warning] 129-129: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (50)
SampoomManagement/Resources/Assets.xcassets/oneline_logo.imageset/Contents.json (1)

1-32: 💡 에셋 구성 확인 완료

기본/라이트/다크 변형이 모두 정의되어 있어 다크 모드 대응에 문제 없겠습니다.

SampoomManagement/Resources/Assets.xcassets/square_logo.imageset/Contents.json (1)

1-32: 💡 에셋 구성 확인 완료

사각 로고 역시 라이트/다크 변형이 적절히 분리되어 있어 UI 일관성이 유지될 것입니다.

SampoomManagement/Info.plist (1)

5-10: 폰트 리소스 번들 포함 여부 확인 필요

Info.plist에 UIAppFonts 항목을 추가하셨는데, 해당 OTF 파일들이 타깃 번들에 실제로 포함되어 있는지 반드시 확인해 주세요. 빠져 있으면 폰트 적용이 되지 않고 기본 폰트로 표시됩니다.

SampoomManagement/Features/Auth/UI/LoginUiState.swift (1)

10-45: LGTM!

UI 상태 구조체가 잘 정의되어 있습니다. isValid 계산 프로퍼티는 이메일과 비밀번호가 비어있지 않고 에러가 없는지 적절하게 검증합니다.

SampoomManagement.xcodeproj/project.pbxproj (1)

12-12: LGTM!

Toast Swift 패키지 통합이 표준 절차에 따라 올바르게 수행되었습니다. 프레임워크 참조, 패키지 의존성, Info.plist 설정이 모두 적절하게 구성되어 있습니다.

Also applies to: 45-45, 92-92, 125-125, 289-289, 324-324, 389-396, 410-414

SampoomManagement/Features/Auth/Data/Remote/DTO/LoginResponseDTO.swift (1)

10-17: LGTM!

로그인 응답 DTO가 명확하게 정의되어 있으며, API 응답 매핑에 필요한 모든 필드를 포함하고 있습니다.

SampoomManagement/Features/Auth/UI/LoginUiEvent.swift (1)

10-14: LGTM!

로그인 UI 이벤트가 명확하게 정의되어 있으며, 이메일/비밀번호 변경 및 제출 이벤트를 적절하게 표현합니다.

SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1)

10-21: LGTM!

LoginResponseDTO에서 User 모델로의 매핑이 명확하고 직관적입니다. 모든 필드가 적절하게 변환되고 있습니다.

SampoomManagement/Features/Auth/Data/Remote/DTO/SignupResponseDTO.swift (1)

10-14: LGTM!

회원가입 응답 DTO가 명확하게 정의되어 있으며, API 응답 매핑에 필요한 필드를 포함하고 있습니다.

SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (2)

31-33: 토큰 존재 여부 검증 로직을 확인하세요.

두 토큰이 모두 존재할 때만 true를 반환하는데, 이는 올바른 접근입니다. 그러나 getAccessToken()getRefreshToken()이 에러를 무시하므로, 키체인 읽기 실패 시 false를 반환하여 사용자가 이미 로그인되어 있어도 로그아웃된 것처럼 보일 수 있습니다.


35-38: 토큰 삭제 실패를 확인하세요.

try?로 삭제 에러를 무시하므로, 키체인 삭제가 실패해도 앱은 사용자가 로그아웃되었다고 가정할 수 있습니다. 최소한 실패를 로깅하거나, 삭제 실패 시 재시도 로직을 추가하는 것을 권장합니다.

SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (2)

20-39: 회원가입 후 자동 로그인 실패 시나리오를 고려하세요.

회원가입이 성공한 후 자동으로 로그인을 시도하는데(Line 38), 만약 로그인이 실패하면 사용자는 회원가입은 되었지만 로그인되지 않은 상태가 됩니다. 이 경우 사용자에게 명확한 에러 메시지를 제공하거나, 회원가입 성공 후 수동 로그인을 유도하는 것이 더 나을 수 있습니다.

또한 회원가입 응답(SignupResponseDTO)을 사용하지 않고 버리고 있는데(Line 28의 _), 필요한 정보가 있다면 활용하는 것을 고려해보세요.


41-51: 토큰 저장 실패를 확인하세요.

preferences.saveToken()이 에러를 조용히 무시하므로(AuthPreferences에서 try? 사용), 토큰 저장에 실패해도 User 객체를 반환합니다. 이는 메모리상에서는 로그인되었지만 재시작 후에는 로그인이 풀리는 불일치 상태를 만들 수 있습니다.

AuthPreferences가 에러를 throw하도록 수정하고, 여기서 적절히 처리하는 것을 권장합니다.

SampoomManagement/App/SampoomManagementApp.swift (4)

12-13: LGTM!

SwiftUI Environment 기반 DI 컨테이너를 앱 레벨에서 초기화하는 접근 방식이 적절합니다.


15-18: LGTM!

앱 초기화 시점에 전역 폰트를 설정하는 것이 적절합니다.


20-24: LGTM!

RootView에 의존성을 주입하는 패턴이 적절하며, 인증 플로우 조율을 위한 구조가 잘 설계되었습니다.


28-35: 전역 폰트 설정의 영향 범위를 검토하세요.

UIKit의 appearance() 프록시를 사용한 전역 폰트 설정은 모든 UILabel, UITextField, UITextView에 영향을 미칩니다. 특정 컴포넌트나 서드파티 라이브러리에서 다른 폰트가 필요한 경우 의도하지 않은 동작이 발생할 수 있습니다.

필요시 개별 컴포넌트에서 명시적으로 폰트를 재설정할 수 있는지 확인하세요.

SampoomManagement/App/RootView.swift (2)

10-22: StateObject 초기화 방식을 확인하세요.

init 내에서 _loginViewModel_signUpViewModelStateObject(wrappedValue:)로 초기화하는 방식은 일반적으로 안전하지만, SwiftUI의 뷰 재생성 시 상태 손실 가능성이 있습니다.

현재 구현은 올바르게 보이지만, 뷰 모델의 상태가 예상치 않게 초기화되는 경우 이 부분을 점검하세요.


73-75: LGTM!

인증 상태 확인 로직이 명확하고 적절하게 추상화되어 있습니다.

SampoomManagement/Features/Auth/UI/LoginViewModel.swift (3)

12-20: LGTM!

@MainActor를 사용하여 UI 업데이트를 메인 스레드에서 처리하는 것이 적절하며, 의존성 주입 패턴도 올바르게 구현되어 있습니다.


22-32: LGTM!

입력 값 변경 시 즉시 유효성 검사를 수행하는 패턴이 사용자 경험 측면에서 적절합니다.


34-55: LGTM!

로그인 제출 로직이 잘 구현되어 있습니다:

  • 유효성 검사 후 제출
  • 비동기 호출 전 값 캡처
  • 적절한 로딩 상태 관리
  • 명확한 에러 처리
SampoomManagement/Core/UI/Components/CommonButton.swift (2)

85-85: LGTM!

폰트를 Gmarket Sans로 변경하고 corner radius를 증가시킨 것이 디자인 시스템 통일성 측면에서 적절합니다. padding(4) 추가도 버튼 내부 여백 개선에 도움이 됩니다.

Also applies to: 94-94, 98-101


110-110: LGTM!

하드코딩된 색상 값을 시맨틱 컬러(.disable, .accent, .textSecondary)로 교체한 것이 유지보수성과 디자인 일관성 측면에서 우수합니다.

Also applies to: 119-119, 127-127

SampoomManagement/Features/Auth/Domain/UseCase/LoginUseCase.swift (1)

10-20: LGTM!

LoginUseCase가 명확한 단일 책임을 가지고 있으며, repository를 적절히 추상화하고 있습니다. 테스트 가능한 구조입니다.

SampoomManagement/Features/Auth/UI/SignUpView.swift (1)

156-172: LGTM! 에러 메시지 처리가 LoginViewModel의 workaround에 의존합니다.

키보드 해제 및 성공/에러 상태 처리가 적절합니다.

참고: Line 168의 타임스탬프 제거 로직은 LoginViewModel.showError의 workaround에 의존합니다(LoginViewModel.swift Line 65). 해당 workaround가 개선되면 이 부분도 함께 단순화할 수 있습니다.

SampoomManagement/Features/Auth/UI/SignUpUiEvent.swift (1)

10-18: 구현이 적절합니다.

회원가입 UI 이벤트를 명확하게 정의한 enum입니다. 각 케이스가 직관적이며 Clean Architecture 패턴을 잘 따르고 있습니다.

SampoomManagement/Core/UI/Extensions/Font+Extension.swift (2)

34-89: 시맨틱 폰트 토큰이 잘 정의되어 있습니다.

일관된 타이포그래피를 위한 시맨틱 폰트 속성들이 명확하게 정의되어 있습니다. 크기와 용도가 주석으로 잘 설명되어 있습니다.


13-30: 검증 완료: Info.plist에 GmarketSansLight, GmarketSansMedium, GmarketSansBold 폰트가 등록되어 있습니다. 커스텀 폰트 구현이 적절합니다.

SampoomManagement/Core/DI/AppDependencies.swift (2)

12-46: 의존성 주입 컨테이너 구현이 적절합니다.

새로운 SwiftUI 기반 DI 컨테이너가 명확하게 구현되어 있습니다. 초기화 시점에 모든 의존성을 수동으로 연결하는 방식이 단순하고 이해하기 쉽습니다.


48-61: ViewModel 팩토리 메서드가 잘 정의되어 있습니다.

각 화면별 ViewModel을 생성하는 팩토리 메서드가 깔끔하게 구현되어 있습니다.

SampoomManagement/Features/Auth/UI/LoginView.swift (2)

11-18: 로그인 뷰 구조가 적절합니다.

ViewModel과 콜백을 통한 상태 관리 및 네비게이션 처리가 깔끔합니다.


104-109: 에러 메시지 처리 로직을 확인하세요.

타임스탬프를 제거하기 위해 "_" 기준으로 메시지를 분리하고 있습니다. 이 로직이 서버에서 전달되는 에러 메시지 형식과 일치하는지 확인해 주세요.

에러 메시지 형식이 문서화되어 있거나 테스트로 검증되었는지 확인하세요. 예상하지 못한 형식의 에러 메시지가 오면 의도한 대로 동작하지 않을 수 있습니다.

SampoomManagement/App/ContentView.swift (1)

15-22: 의존성 주입 방식 전환이 적절합니다.

새로운 AppDependencies를 사용한 의존성 주입으로 깔끔하게 전환되었습니다. StateObject를 통한 ViewModel 초기화도 올바르게 구현되어 있습니다.

SampoomManagement/Core/Utilities/KeyboardObserver.swift (1)

11-37: 키보드 옵저버 구현이 적절합니다.

Combine을 활용한 키보드 높이 추적이 깔끔하게 구현되어 있습니다. 메모리 누수 방지를 위한 weak self 사용과 애니메이션 처리도 적절합니다.

SampoomManagement/Features/Auth/UI/SignUpUiState.swift (3)

10-65: UI State 구조가 적절합니다.

회원가입에 필요한 모든 필드와 에러 상태를 명확하게 정의하고 있습니다. 기본값으로 workspace를 "대리점"으로 설정한 것도 비즈니스 요구사항을 반영한 것으로 보입니다.


67-80: 유효성 검사 로직이 적절합니다.

모든 필드가 비어있지 않고 에러가 없는지 확인하는 간결한 구현입니다.


82-118: 불변 업데이트를 위한 copy 메서드가 잘 구현되어 있습니다.

옵셔널 래핑(String??)을 사용하여 "업데이트 안 함"과 "nil로 설정"을 구분하는 패턴이 적절하게 적용되어 있습니다. LoginUiState의 copy 메서드와 일관된 구현입니다.

SampoomManagement/Features/Auth/Domain/AuthValidator.swift (3)

12-17: LGTM!

빈 값 검증 로직이 명확하고 올바릅니다. 공백 처리도 적절합니다.


20-35: 이메일 검증 로직 확인 완료

정규식 패턴을 사용한 이메일 형식 검증이 적절하게 구현되어 있습니다. 일반적인 이메일 형식을 처리하기에 충분하지만, RFC 5322의 모든 경우를 커버하지는 않습니다(예: 특수 문자, 인용 부호 등). 현재 애플리케이션의 요구사항에는 충분해 보입니다.


59-69: LGTM!

비밀번호 확인 검증 로직이 올바르게 구현되어 있습니다.

SampoomManagement/Core/Resources/StringResources.swift (1)

74-111: LGTM!

인증 관련 문자열 리소스가 잘 구성되어 있습니다. 로그인과 회원가입에 필요한 레이블, 플레이스홀더, 버튼 텍스트, 검증 메시지가 모두 포함되어 있습니다. fieldRequired 함수를 통한 동적 메시지 생성도 적절합니다.

SampoomManagement/Core/UI/Components/CommonTextField.swift (2)

54-79: LGTM!

내부 상태에서 @Binding으로 전환한 것은 폼 필드 관리에 적합한 접근 방식입니다. 에러 상태(isError, errorMessage)를 추가하여 유효성 검증 피드백을 제공하는 것도 좋습니다.


171-179: LGTM!

테두리 색상 우선순위 로직(에러 > 포커스 > 기본)이 명확하고 사용자 경험에 적합합니다.

SampoomManagement/Features/Auth/Data/Local/Preferences/KeychainManager.swift (3)

45-71: LGTM!

키체인에서 값을 가져오는 로직이 올바르게 구현되어 있습니다. 항목이 없을 때 nil 반환, 다른 에러 발생 시 throw, 디코딩 실패 시 nil 반환하는 처리가 적절합니다.


73-85: LGTM!

삭제 메서드가 멱등성(idempotent)을 보장하도록 구현되어 있어 좋습니다. 항목이 없는 경우도 성공으로 처리하는 것이 적절합니다.


87-99: LGTM!

서비스 범위 내 모든 항목을 삭제하는 로직이 올바릅니다.

SampoomManagement/Features/Auth/UI/SignUpViewModel.swift (3)

23-59: LGTM!

각 필드 업데이트 메서드가 즉시 검증을 수행하여 실시간 피드백을 제공하는 것이 좋습니다. Lines 50-52에서 비밀번호 변경 시 비밀번호 확인 필드도 재검증하는 로직이 적절합니다.


62-97: LGTM!

회원가입 제출 로직이 올바르게 구현되어 있습니다. 모든 필드 검증 후 진행, 값 캡처, 로딩 상태 관리, 에러 처리가 적절합니다.


112-152: LGTM!

모든 검증 메서드가 일관된 패턴을 따르고 있어 코드 가독성과 유지보수성이 좋습니다. AuthValidator를 활용한 검증 로직이 적절합니다.

Comment thread SampoomManagement/Core/UI/Components/CommonTextField.swift Outdated
Comment thread SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift Outdated
Comment thread SampoomManagement/Features/Auth/UI/LoginViewModel.swift
Copy link
Copy Markdown
Member

@CHOOSLA CHOOSLA left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
SampoomManagement/Core/UI/Components/CommonTextField.swift (1)

147-157: 사용되지 않는 computed property 제거

buttonBackgroundColor가 정의되어 있지만 body에서 사용되지 않습니다.

다음과 같이 사용되지 않는 코드를 제거하세요:

-    private var buttonBackgroundColor: Color {
-        // 다크모드 고려한 기본 배경색
-        switch colorScheme {
-        case .dark:
-            return Color(red: 0.1, green: 0.1, blue: 0.1) // 매우 어두운 회색
-        case .light:
-            return .white
-        @unknown default:
-            return Color(.systemBackground)
-        }
-    }
-    
♻️ Duplicate comments (1)
SampoomManagement/Core/UI/Components/CommonTextField.swift (1)

129-131: 이전 리뷰의 제안이 적용되었습니다

onChange 핸들러에서 사용되지 않는 oldValue 파라미터를 _로 변경하여 SwiftLint 경고가 해결되었습니다.

🧹 Nitpick comments (1)
SampoomManagement/Core/Network/NetworkError.swift (1)

33-48: 네트워크 에러 케이스 중복을 개선하세요.

AuthError.networkErrorNetworkError.networkError 케이스가 중복됩니다. 이는 다음과 같은 문제를 야기할 수 있습니다:

  • 어떤 에러 타입을 사용해야 할지 혼란 발생
  • 에러 처리 로직의 일관성 저하
  • 코드 중복으로 인한 유지보수성 감소

다음 방법들을 고려해보세요:

  1. 인증 관련 네트워크 에러는 NetworkError를 재사용하고 AuthError는 순수 인증 로직 에러만 처리
  2. AuthError.networkErrorAuthError.authNetworkError로 명확하게 네이밍

예시: NetworkError 재사용 방식

 enum AuthError: Error, LocalizedError {
     case tokenSaveFailed(Error)
     case invalidCredentials
-    case networkError(Error)
     
     var errorDescription: String? {
         switch self {
         case .tokenSaveFailed(let error):
             return "토큰 저장 실패: \(error.localizedDescription)"
         case .invalidCredentials:
             return "잘못된 인증 정보입니다"
-        case .networkError(let error):
-            return "네트워크 오류: \(error.localizedDescription)"
         }
     }
 }

그리고 네트워크 관련 에러는 NetworkError를 직접 사용하도록 호출 코드를 조정하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df55ce5 and d2e86c7.

📒 Files selected for processing (5)
  • SampoomManagement/Core/Network/NetworkError.swift (1 hunks)
  • SampoomManagement/Core/UI/Components/CommonTextField.swift (4 hunks)
  • SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (1 hunks)
  • SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (1 hunks)
  • SampoomManagement/Features/Auth/UI/LoginViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • SampoomManagement/Features/Auth/UI/LoginViewModel.swift
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-11T12:58:56.474Z
Learnt from: Sangyoon98
PR: 33-Auto/Sampoom-Management-iOS#7
File: SampoomManagement/Core/UI/Components/CommonTextField.swift:117-119
Timestamp: 2025-10-11T12:58:56.474Z
Learning: In Swift/SwiftUI iOS 17.0+, the onChange(of:perform:) modifier expects a two-parameter closure (oldValue, newValue) or zero-parameter closure, not a single-parameter closure. The older single-parameter version was deprecated in iOS 17.0.

Applied to files:

  • SampoomManagement/Core/UI/Components/CommonTextField.swift
🧬 Code graph analysis (2)
SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (3)
SampoomManagement/Features/Auth/Data/Remote/API/AuthAPI.swift (2)
  • signup (45-86)
  • login (19-42)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (3)
  • saveToken (18-28)
  • clear (42-50)
  • hasToken (38-40)
SampoomManagement/Features/Auth/Data/Mappers/AuthMappers.swift (1)
  • toModel (11-20)
SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (1)
SampoomManagement/Features/Auth/Data/Local/Preferences/KeychainManager.swift (3)
  • save (24-43)
  • delete (73-85)
  • get (45-71)
🔇 Additional comments (10)
SampoomManagement/Core/UI/Components/CommonTextField.swift (3)

53-79: 외부 바인딩 및 에러 상태 처리 잘 구현됨

@Binding을 통한 외부 상태 관리와 @FocusState를 활용한 포커스 제어, 그리고 에러 상태 처리가 적절하게 추가되었습니다. SwiftUI 모범 사례를 잘 따르고 있습니다.


114-115: 중복 패딩 사용 확인 필요

size.padding을 적용한 후 추가로 .padding(4)를 적용하고 있습니다. 이는 모든 방향에 4포인트의 추가 패딩을 더하는데, 의도된 디자인인지 확인이 필요합니다.

.padding(size.padding)  // EdgeInsets with per-side values
.padding(4)             // Uniform 4pt padding on all sides

만약 border와 내부 콘텐츠 사이에 추가 여백을 두려는 의도라면 명확한 주석을 추가하거나, 더 명시적인 방식으로 구현하는 것을 고려해보세요.


227-273: Preview 구현이 다양한 사용 사례를 잘 보여줍니다

외부 바인딩을 사용한 프리뷰 코드가 이메일, 비밀번호, 에러 상태 등 다양한 케이스를 명확하게 시연하고 있습니다.

SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift (3)

18-28: 이전 리뷰 지적 사항이 올바르게 수정되었습니다.

토큰 저장 실패 시 롤백 로직이 추가되어 부분 저장으로 인한 불일치 상태를 방지합니다. 에러를 호출자에게 전파하여 적절한 처리가 가능합니다.


38-40: getter 메서드가 수정되면 이 메서드도 업데이트 필요합니다.

getAccessToken()getRefreshToken()이 에러를 throw하도록 변경되면, 이 메서드도 그에 맞춰 에러 처리 로직을 추가해야 합니다. 키체인 접근 실패 시 false를 반환하는 것이 적절한지, 아니면 에러를 전파해야 하는지 결정해야 합니다.

예시 개선안:

func hasToken() -> Bool {
    do {
        return try getAccessToken() != nil && try getRefreshToken() != nil
    } catch {
        // 키체인 접근 오류 시 로깅
        print("AuthPreferences - 키체인 접근 실패: \(error)")
        return false  // 또는 에러를 throw
    }
}

42-50: 로그아웃 시나리오에 적합한 에러 처리입니다.

삭제 실패 시에도 로그아웃 상태로 간주하는 것이 합리적이며, 에러 로깅을 통해 디버깅이 가능합니다.

SampoomManagement/Features/Auth/Data/Repository/AuthRepositoryImpl.swift (4)

14-17: 깔끔한 의존성 주입 구조입니다.


41-57: 토큰 저장 실패에 대한 에러 처리가 잘 구현되어 있습니다.

키체인 저장 실패 시 로깅 후 AuthError.tokenSaveFailed로 감싸서 전파하는 것이 적절합니다. 호출자가 명확한 에러 타입을 받을 수 있습니다.


63-65: AuthPreferences.hasToken()의 에러 처리 개선이 필요합니다.

이 메서드는 preferences.hasToken()에 의존하는데, 해당 메서드가 키체인 접근 오류와 토큰 부재를 구분하지 못합니다. AuthPreferences.swift의 getter 메서드들이 수정되면 여기도 함께 업데이트되어야 합니다.


28-38: 회원가입 API가 토큰을 반환하지 않으므로 회원가입 후 별도 로그인 호출이 필요합니다.

Likely an incorrect or invalid review comment.

Comment thread SampoomManagement/Features/Auth/Data/Local/Preferences/AuthPreferences.swift Outdated
Copy link
Copy Markdown

@taemin3 taemin3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!

Copy link
Copy Markdown

@yangjiseonn yangjiseonn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge 3명 이상의 리뷰어에게 승인되어 병합 준비가 완료된 PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants