-
Notifications
You must be signed in to change notification settings - Fork 0
[FIX] 회원가입 관련 오류 수정 #174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FIX] 회원가입 관련 오류 수정 #174
Conversation
- 델리게이트의 생명주기를 메서드가 아닌 상위 클래스가 관리하도록 수정 - 인증 컨트롤러를 한번만 생성하도록 수정
WalkthroughKeychain logging adjusted and Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as User
participant AppleSvc as AppleLoginService
participant ASCtrl as ASAuthorizationController
participant Delegate as ASAuthorizationControllerDelegate
participant Subject as PublishSubject<AuthServiceResponse>
User->>AppleSvc: fetchUserCredential()
activate AppleSvc
AppleSvc->>AppleSvc: subject = PublishSubject
AppleSvc->>ASCtrl: performRequests() (via stored controller)
deactivate AppleSvc
ASCtrl-->>Delegate: authorization completed
activate Delegate
Delegate->>Subject: onNext(AuthServiceResponse) or onError
deactivate Delegate
Note over Subject: subscribers receive AuthServiceResponse observable
sequenceDiagram
autonumber
actor User as User
participant Reactor as MyPage/Withdrawl Reactor
participant UD as UserDefaults
participant KC as KeyChainService
User->>Reactor: Logout / Withdraw complete
Reactor->>UD: clear except lastLogin
loop for each TokenType.allCases
Reactor->>KC: deleteToken(type:)
KC-->>Reactor: status
end
Reactor-->>User: completed
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (4)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (4)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (12)
Poppool/DataLayer/Data/Data/Network/Provider/ProviderImpl.swift (1)
99-108: 헤더 키 대소문자 의존 제거 및 토큰 저장 분리HTTP 헤더 키는 RFC 상 대소문자 비민감이지만,
allHeaderFields딕셔너리는 키 문자열의 대소문자를 그대로 담아옵니다. 현재처럼 하드코딩된 키 문자열 하나에 의존하면 환경/서버 변경 시 토큰 수집이 실패할 수 있습니다. 또한 두 토큰을 모두 발견해야만 저장하는 조건은 불필요하게 엄격합니다.아래처럼 케이스-인센시티브 조회로 변경하고, 각 토큰을 독립적으로 저장하세요.
- // 만약 헤더에 새 토큰이 있으면 저장 - if var accessToken = response.response?.allHeaderFields["Authorization"] as? String, - var refreshToken = response.response?.allHeaderFields["Authorization-refresh"] as? String { - accessToken = accessToken.replacingOccurrences(of: "Bearer ", with: "") - refreshToken = refreshToken.replacingOccurrences(of: "Bearer ", with: "") - - @Dependency var keyChainService: KeyChainService - keyChainService.saveToken(type: .accessToken, value: accessToken) - keyChainService.saveToken(type: .refreshToken, value: refreshToken) - } + // 헤더에서(대소문자 무시) 새 토큰이 있으면 각각 저장 + if let headers = response.response?.allHeaderFields { + func headerValue(_ name: String) -> String? { + headers.first { (key, _) in + (key as? String)?.lowercased() == name.lowercased() + }?.value as? String + } + @Dependency var keyChainService: KeyChainService + if var access = headerValue("authorization") { + access = access.replacingOccurrences(of: "Bearer ", with: "") + keyChainService.saveToken(type: .accessToken, value: access) + } + if var refresh = headerValue("authorization-refresh") { + refresh = refresh.replacingOccurrences(of: "Bearer ", with: "") + keyChainService.saveToken(type: .refreshToken, value: refresh) + } + }보완: 서버가
Authorization-Refresh/X-Refresh-Token등으로 바뀌는 경우를 대비해, 후보 키 배열을 순회하도록 확장하는 것도 고려할 수 있습니다.Poppool/DataLayer/Data/Data/Network/Service/AppleLoginService.swift (3)
71-77: authorizationCode 누락/변환 실패 시 onError 없이 return — 구독자 영구 대기오류를 확정적으로 방출하고 상태를 리셋하세요.
- guard let authorizationCode = appleIDCredential.authorizationCode else { - return - } + guard let authorizationCode = appleIDCredential.authorizationCode else { + authServiceResponse.onError(AuthError.unknownError(description: "AppleLogin AuthorizationCode is Not Found")) + isAuthorizing = false + authorizationController = nil + return + } @@ - guard let convertAuthorizationCode = String(data: authorizationCode, encoding: .utf8) else { - return - } + guard let convertAuthorizationCode = String(data: authorizationCode, encoding: .utf8) else { + authServiceResponse.onError(AuthError.unknownError(description: "AppleLogin AuthorizationCode Convert Fail")) + isAuthorizing = false + authorizationController = nil + return + }
78-80: 민감정보(ID Token, Auth Code) 로깅 금지토큰/코드는 자격증명입니다. 로그에 남기면 보안사고로 직결됩니다. 성공 여부만 기록하거나 마스킹하세요.
- Logger.log("IDToken: \(idToken)", category: .info) - Logger.log("Auth Code: \(convertAuthorizationCode)", category: .info) + Logger.log("Apple login succeeded", category: .info) authServiceResponse.onNext(.init(idToken: idToken, authorizationCode: convertAuthorizationCode)) + authServiceResponse.onCompleted() + isAuthorizing = false + authorizationController = nil마스킹이 필요하면:
Logger.log("Apple login succeeded (idToken=***\(idToken.suffix(4)))", category: .info)
86-95: 실패 콜백에서도 상태 리셋 필요진행 상태/컨트롤러를 해제하지 않으면 다음 요청이 막히거나 중첩될 수 있습니다.
Logger.log( "AppleLogin Fail", category: .error ) authServiceResponse.onError(error) + isAuthorizing = false + authorizationController = nilPoppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift (1)
82-86: 하단 Safe Area 반영 권장홈 인디케이터가 있는 기기에서 버튼이 가려질 수 있습니다. Safe Area로 변경을 제안합니다.
- make.bottom.equalToSuperview() + make.bottom.equalTo(self.safeAreaLayoutGuide)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift (2)
73-76: 멀티라인 타이틀의 고정 높이(56pt) 제거 필요numberOfLines=0인데 높이를 56pt로 고정하면 Dynamic Type/다국어에서 잘림 위험이 큽니다. intrinsic size에 맡기고 고정 높이를 제거해주세요.
titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().inset(64) make.leading.trailing.equalToSuperview().inset(20) - make.height.equalTo(56) + // 고정 높이 제거: Dynamic Type/현지화 대응 }
112-117: 하단 버튼 safeArea 미적용홈 인디케이터와 겹칠 수 있습니다. safeArea로 앵커 변경 권장.
- completeButton.snp.makeConstraints { make in - make.leading.trailing.equalToSuperview().inset(20) - make.bottom.equalToSuperview() - make.height.equalTo(52) - } + completeButton.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(20) + make.bottom.equalTo(self.safeAreaLayoutGuide).inset(20) + make.height.equalTo(52) + }Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift (2)
63-67: 단일/복합 라벨의 고정 높이 제거 권장라벨들에 28/22/18pt 고정 높이가 지정되어 있어 Dynamic Type/현지화 시 잘림 위험이 있습니다. 고정 높이를 제거하고 intrinsic size에 맡기세요.
- make.height.equalTo(28) + // 고정 높이 제거- make.height.equalTo(28) + // 고정 높이 제거- make.height.equalTo(28) + // 고정 높이 제거- make.height.equalTo(22) + // 고정 높이 제거- make.height.equalTo(18) + // 고정 높이 제거Also applies to: 70-74, 77-81, 84-88, 91-95
98-101: 하단 버튼 스택 safeArea로 내려주세요현재는 superview의 bottom에 붙어 있어 기종에 따라 겹침 가능성이 있습니다.
- buttonStackView.snp.makeConstraints { make in - make.leading.trailing.bottom.equalToSuperview().inset(20) - make.height.equalTo(52) - } + buttonStackView.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(20) + make.bottom.equalTo(self.safeAreaLayoutGuide).inset(20) + make.height.equalTo(52) + }Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift (1)
94-97: 하단 버튼 safeArea 미적용홈 인디케이터와 겹침 가능성이 있습니다. safeArea로 변경 권장.
- bottomButton.snp.makeConstraints { make in - make.leading.trailing.bottom.equalToSuperview().inset(20) - make.height.equalTo(52) - } + bottomButton.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(20) + make.bottom.equalTo(self.safeAreaLayoutGuide).inset(20) + make.height.equalTo(52) + }Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift (2)
79-83: 다수 라벨의 고정 높이 제거 권장Dynamic Type/현지화 대응을 위해 라벨 높이 고정을 제거하세요.
- make.height.equalTo(28) + // 고정 높이 제거- make.height.equalTo(28) + // 고정 높이 제거- make.height.equalTo(28) + // 고정 높이 제거- make.height.equalTo(22) + // 고정 높이 제거- make.height.equalTo(18) + // 고정 높이 제거- make.height.equalTo(20) + // 고정 높이 제거- make.height.equalTo(20) + // 고정 높이 제거- make.height.equalTo(72) + // 고정 높이 제거(내부 컴포넌트에 맞게 intrinsic 활용)Also applies to: 86-90, 93-97, 100-104, 107-111, 114-118, 127-131, 135-138
141-144: 하단 버튼 스택 safeArea로 이동superview bottom 대신 safeArea bottom으로 변경해주세요.
- buttonStackView.snp.makeConstraints { make in - make.leading.trailing.bottom.equalToSuperview().inset(20) - make.height.equalTo(52) - } + buttonStackView.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(20) + make.bottom.equalTo(self.safeAreaLayoutGuide).inset(20) + make.height.equalTo(52) + }
🧹 Nitpick comments (18)
Poppool/Poppool/Resource/Info.plist (1)
58-59: 비표준 Info.plist 키UIDesignRequiresCompatibility제거
해당 키는 공식적으로 지원되지 않으며 코드 내 사용처가 전혀 확인되지 않으므로 삭제를 권장합니다.Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift (1)
49-52: 로그 메시지 오타/레벨 정정 제안
- 문법/오타: "Failed to fetched" → "Failed to fetch", "Faied" → "Failed", "deleted" 시제 → "delete".
- 의미: 저장 성공 메시지의 전치사 "from KeyChain" → "to KeyChain"이 자연스럽습니다.
- 레벨: 실패 로그는
.error가 적절합니다(현재.info).- Logger.log( - "Failed to fetched \(type.rawValue) from KeyChain", - category: .info - ) + Logger.log( + "Failed to fetch \(type.rawValue) from KeyChain", + category: .error + )- Logger.log( - "Successfully saved \(type.rawValue) from KeyChain", - category: .info - ) + Logger.log( + "Successfully saved \(type.rawValue) to KeyChain", + category: .info + )- Logger.log( - "Faied to save \(type.rawValue) from KeyChain", - category: .info - ) + Logger.log( + "Failed to save \(type.rawValue) to KeyChain", + category: .error + )- Logger.log( - "Faied to deleted \(type.rawValue) from KeyChain", - category: .info - ) + Logger.log( + "Failed to delete \(type.rawValue) from KeyChain", + category: .error + )추가: 함수 주석의 반환 타입 표기가 실제 구현(Result)과 불일치합니다(주석의 Single/Completable). 이후 커밋에서 문서화 정합성도 맞춰주세요.
Also applies to: 88-90, 93-96, 123-126
Poppool/PresentationLayer/Presentation/Presentation/Scene/MyPage/Main/MyPageReactor.swift (1)
172-174: 토큰 삭제 로직 중복 제거여기서
TokenType.allCases로 모두 삭제하고, 아래reduce(.logout)에서도 개별 토큰을 다시 삭제합니다(라인 201-205). 기능상 문제는 없지만 중복 호출/로그만 늘어납니다. 한 곳으로 통합을 권장합니다. mutate의.do(onCompleted:)에 모으는 쪽을 제안합니다.- case .logout: - @Dependency var keyChainService: KeyChainService - keyChainService.deleteToken(type: .accessToken) - keyChainService.deleteToken(type: .refreshToken) + case .logout: + // 토큰 삭제는 mutate(.logoutButtonTapped)에서 일괄 처리됨또한 DI 일관성을 위해
@Dependency private var keyChainService를 클래스 프로퍼티로 승격해 재사용하는 것도 고려해주세요.Poppool/PresentationLayer/Presentation/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swift (1)
113-114: 토큰 삭제 중복 제거이미 바로 위(라인 110-112)에서
.accessToken/.refreshToken을 삭제한 뒤, 다시TokenType.allCases로 중복 삭제하고 있습니다. 한 방식만 유지하면 됩니다. 향후 토큰 타입 추가를 고려하면allCases만 남기는 편이 낫습니다.- keyChainService.deleteToken(type: .accessToken) - keyChainService.deleteToken(type: .refreshToken) - UserDefaultService.Key.allCases.forEach { userDefaultService.delete(keyType: $0) } - TokenType.allCases.forEach { keyChainService.deleteToken(type: $0) } + UserDefaultService.Key.allCases.forEach { userDefaultService.delete(keyType: $0) } + TokenType.allCases.forEach { keyChainService.deleteToken(type: $0) }Poppool/DataLayer/Data/Data/Network/Service/AppleLoginService.swift (2)
43-45: 오탈자: windowSecne → windowScene가독성·일관성.
- let windowSecne = scenes.first as? UIWindowScene - guard let window = windowSecne?.windows.first else { + let windowScene = scenes.first as? UIWindowScene + guard let window = windowScene?.windows.first else {
30-35: (선택) Single로 의미론 정렬단일 성공/실패만 방출하므로
Single<AuthServiceResponse>가 더 적합합니다. 인터페이스 호환성 이슈가 없다면 변경을 고려하세요.Poppool/PresentationLayer/SearchFeature/SearchFeature/Common/View/Component/TagCollectionHeaderView.swift (1)
53-57: 터치 타깃 44pt 보장 및 고정 높이 제거 제안
현 20pt는 HIG 권장(44pt)에 미달합니다. 인셋으로 시각적 높이는 유지하면서 터치 면적만 키우는 접근을 권장합니다.아래와 같이 수정 제안:
@@ - removeAllButton.snp.makeConstraints { make in + removeAllButton.snp.makeConstraints { make in make.trailing.equalToSuperview() make.centerY.equalTo(sectionTitleLabel) - make.height.equalTo(20) + make.height.greaterThanOrEqualTo(44) }그리고 초기화부에서 인셋 추가:
- let removeAllButton = PPUnderlinedTextButton(fontStyle: .KOr13, text: "모두삭제").then { - $0.isHidden = true - } + let removeAllButton = PPUnderlinedTextButton(fontStyle: .KOr13, text: "모두삭제").then { + $0.isHidden = true + $0.contentEdgeInsets = UIEdgeInsets(top: 12, left: 8, bottom: 12, right: 8) + }Also applies to: 15-17
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedView.swift (1)
15-17: 나이 범위 0...100 하드코딩 — 도메인 요구 검증 권장
법적/서비스 정책 최소 연령이 있다면 상수/설정으로 관리하세요(예: minAge). 현 하드코딩은 추후 변경에 취약합니다.예) 외부 주입/상수화:
let ageRange = (minAge...maxAge).map { "\($0)세" }Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPUnderlinedTextButton.swift (3)
10-12: 프로퍼티 네이밍(로어카멜) 및 1px 헤어라인 보장
식별자 대소문자 컨벤션과 디바이스 스케일별 1px 라인을 권장합니다.- private let UnderlineView = UIView().then { + private let underlineView = UIView().then { $0.backgroundColor = .g1000 } @@ - UnderlineView.backgroundColor = textColor + underlineView.backgroundColor = textColor @@ - UnderlineView.backgroundColor = disabledTextColor + underlineView.backgroundColor = disabledTextColor @@ - [UnderlineView].forEach { + [underlineView].forEach { self.addSubview($0) } @@ - UnderlineView.snp.makeConstraints { make in - make.height.equalTo(1) + underlineView.snp.makeConstraints { make in + make.height.equalTo(1.0 / UIScreen.main.scale) make.bottom.equalToSuperview() make.horizontalEdges.equalToSuperview() }Also applies to: 35-41, 47-50, 53-57
32-42: state 변화 시 밑줄 색 반영 단순화
isEnabled기준으로 색을 정하면 하이라이팅 등 추가 state에도 안정적입니다.- switch state { - case .normal: - UnderlineView.backgroundColor = textColor - case .disabled: - UnderlineView.backgroundColor = disabledTextColor - default: break - } + underlineView.backgroundColor = isEnabled ? textColor : disabledTextColor추가로, 레이아웃 패스가 없을 때도 반영되도록 다음을 클래스 내부에 보강 권장:
public override var isEnabled: Bool { didSet { underlineView.backgroundColor = isEnabled ? textColor : disabledTextColor } }
45-59: 내부 유틸 메서드 접근 제어 명시
외부 노출 불필요합니다. private 지정 권장.-extension PPUnderlinedTextButton { - func addViews() { +extension PPUnderlinedTextButton { + private func addViews() { @@ - func setupConstraints() { + private func setupConstraints() {Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift (1)
59-61: 카테고리 비어있는 경우 UX 확인 필요
state.categoryTitles가 빈 배열이면categoryString가 빈 문자열이 되어 이후 문장 앞부분(“와 …”)이 어색할 수 있습니다. 빈 배열 시 안내 문구를 대체하거나 해당 구문을 숨기는 처리를 고려해주세요.Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
34-44: nil 컬러 처리 일관성 개선현재는
attributedText가 있을 때 nil을.g1000으로 강제, 없을 때는textColor = nil로 기본색으로 돌아갑니다. 분기별 동작이 달라 예측이 어렵습니다. nil을 “기본 색으로 복귀”로 통일하는 편이 자연스럽습니다(또는 둘 다.g1000으로 통일).아래 예시는 “기본 색으로 복귀”로 통일하고, attributed 분기에서는 컬러 속성을 제거합니다.
- func updateTextColor(to color: UIColor?) { - if let current = self.attributedText, current.length > 0 { - let mutable = NSMutableAttributedString(attributedString: current) - let fullRange = NSRange(location: 0, length: mutable.length) - mutable.addAttribute(.foregroundColor, value: color ?? .g1000, range: fullRange) - self.attributedText = mutable - } else { - self.textColor = color - } - } + func updateTextColor(to color: UIColor?) { + if let current = self.attributedText, current.length > 0 { + let mutable = NSMutableAttributedString(attributedString: current) + let fullRange = NSRange(location: 0, length: mutable.length) + if let color { + mutable.addAttribute(.foregroundColor, value: color, range: fullRange) + } else { + mutable.removeAttribute(.foregroundColor, range: fullRange) + } + self.attributedText = mutable + } else { + self.textColor = color ?? .label + } + }Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift (1)
94-103: 접근성 값도 함께 갱신해주세요선택된 나이를 VoiceOver가 읽을 수 있도록 accessibilityValue를 업데이트하는 편이 좋습니다.
func injection(with input: Input) { if let age = input.age { verticalStackView.isHidden = false ageLabel.updateText(to: "\(age)세") defaultLabel.isHidden = true } else { verticalStackView.isHidden = true defaultLabel.isHidden = false } + // 접근성 + self.isAccessibilityElement = true + self.accessibilityLabel = "나이" + self.accessibilityValue = input.age.map { "\($0)세" } ?? "미선택" }Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift (1)
50-61: 셀 접근성 속성 업데이트 제안선택 상태를 보조기기에서 인지할 수 있도록 traits/label을 설정하는 것을 권장합니다.
func injection(with input: Input) { titleLabel.updateText(to: input.title) if input.isSelected { contentView.backgroundColor = .blu500 contentView.layer.borderWidth = 0 titleLabel.updateTextColor(to: .w100) } else { contentView.backgroundColor = .clear contentView.layer.borderWidth = 1 titleLabel.updateTextColor(to: .g400) } + // 접근성 + isAccessibilityElement = true + accessibilityLabel = input.title + accessibilityTraits = input.isSelected ? [.button, .selected] : [.button] }Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift (1)
58-62: 타이틀 고정 높이 제거 제안두 줄 텍스트/다국어/다이내믹 타입에서 잘림 우려가 있습니다. 고정 높이를 제거하고 intrinsic 크기에 맡기는 것을 권장합니다.
make.leading.trailing.equalToSuperview().inset(20) - make.height.equalTo(56)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift (2)
15-17: PPLabel 색상 지정은 updateTextColor 사용 권장디자인시스템 일관성을 위해 textColor 직접 지정 대신 updateTextColor(to:)를 사용하는 편이 안전합니다(속성이 attributedText로 전환돼도 대응).
- private let descriptionLabel = PPLabel(text: "이후 이 별명으로 팝풀에서 활동할 예정이에요.", style: .KOr15).then { - $0.textColor = .g600 - } + private let descriptionLabel = PPLabel(text: "이후 이 별명으로 팝풀에서 활동할 예정이에요.", style: .KOr15).then { + $0.updateTextColor(to: .g600) + }
19-21: 버튼 라벨(확인/다음) 불일치 — UX 혼란 가능성초기 비활성 상태는 "다음", 활성은 "확인"으로 달라 사용자 혼란 소지가 있습니다. 단계 흐름과 맞춰 통일해 주세요.
- let completeButton = PPButton(buttonStyle: .primary, text: "확인", disabledText: "다음").then { + let completeButton = PPButton(buttonStyle: .primary, text: "다음", disabledText: "다음").then { $0.isEnabled = false }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (27)
Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift(3 hunks)Poppool/DataLayer/Data/Data/Network/Provider/ProviderImpl.swift(1 hunks)Poppool/DataLayer/Data/Data/Network/Service/AppleLoginService.swift(1 hunks)Poppool/DataLayer/Data/Data/RepositoryImpl/SignUpRepositoryImpl.swift(1 hunks)Poppool/Poppool.xcodeproj/project.pbxproj(2 hunks)Poppool/Poppool/Resource/Info.plist(2 hunks)Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPButton.swift(3 hunks)Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPUnderlinedTextButton.swift(1 hunks)Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UIButton+.swift(2 hunks)Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift(1 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/MyPage/Main/MyPageReactor.swift(1 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swift(1 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift(1 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift(3 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpCheckBoxButton.swift(1 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift(2 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift(2 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step2/SignUpStep2Controller.swift(1 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step2/SignUpStep2View.swift(3 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift(2 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift(2 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedView.swift(2 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift(3 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift(3 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift(1 hunks)Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/TermsDetail/TermsDetailView.swift(1 hunks)Poppool/PresentationLayer/SearchFeature/SearchFeature/Common/View/Component/TagCollectionHeaderView.swift(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: 0Hooni
PR: PopPool/iOS#164
File: Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift:61-63
Timestamp: 2025-07-21T09:55:51.700Z
Learning: PopPool iOS 프로젝트의 KeyChainService에서 saveToken과 deleteToken 메서드에 discardableResult를 사용하는 것은 의도적인 설계이다. 사용하는 곳에서 throws와 error 처리를 간편하게 만들고 return 값을 선택적으로 무시할 수 있도록 하기 위한 목적이다.
📚 Learning: 2025-07-21T09:55:51.700Z
Learnt from: 0Hooni
PR: PopPool/iOS#164
File: Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift:61-63
Timestamp: 2025-07-21T09:55:51.700Z
Learning: PopPool iOS 프로젝트의 KeyChainService에서 saveToken과 deleteToken 메서드에 discardableResult를 사용하는 것은 의도적인 설계이다. 사용하는 곳에서 throws와 error 처리를 간편하게 만들고 return 값을 선택적으로 무시할 수 있도록 하기 위한 목적이다.
Applied to files:
Poppool/PresentationLayer/Presentation/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swiftPoppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift
🧬 Code graph analysis (15)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step2/SignUpStep2Controller.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (2)
updateText(25-32)updateTextColor(35-44)
Poppool/PresentationLayer/Presentation/Presentation/Scene/MyPage/Withdrawl/SelectedReason/WithdrawlReasonReactor.swift (1)
Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift (1)
deleteToken(104-129)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
updateText(25-32)
Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift (1)
Poppool/CoreLayer/Infrastructure/Infrastructure/Logger/Logger.swift (1)
log(84-106)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
updateText(25-32)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
updateText(25-32)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
updateText(25-32)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
updateTextColor(35-44)
Poppool/PresentationLayer/Presentation/Presentation/Scene/MyPage/Main/MyPageReactor.swift (1)
Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift (1)
deleteToken(104-129)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPUnderlinedTextButton.swift (1)
Poppool/PresentationLayer/SearchFeature/SearchFeature/Common/View/Component/TagCollectionHeaderView.swift (2)
addViews(40-44)setupConstraints(46-58)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPButton.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UIButton+.swift (1)
setText(5-27)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (2)
updateText(25-32)updateTextColor(35-44)
Poppool/DataLayer/Data/Data/Network/Service/AppleLoginService.swift (5)
Poppool/DataLayer/Data/Data/Network/Service/KakaoLoginService.swift (1)
fetchUserCredential(30-57)Poppool/DataLayer/Data/Data/RepositoryImpl/AppleLoginRepositoryImpl.swift (1)
fetchUserCredential(12-14)Poppool/DataLayer/Data/Data/RepositoryImpl/KakaoLoginRepositoryImpl.swift (1)
fetchUserCredential(12-14)Poppool/DomainLayer/Domain/Domain/UseCaseImpl/AppleLoginUseCaseImpl.swift (1)
fetchUserCredential(14-16)Poppool/DomainLayer/Domain/Domain/UseCaseImpl/KakaoLoginUseCaseImpl.swift (1)
fetchUserCredential(14-16)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
updateTextColor(35-44)
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UILabel+.swift (1)
updateTextColor(35-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: 🏗️ Build Workflow
🔇 Additional comments (29)
Poppool/Poppool.xcodeproj/project.pbxproj (1)
447-447: ITSAppUsesNonExemptEncryption = NO를 빌드 설정으로 이동한 점은 합리적입니다. 배포 시 수출 규정 설문과의 일치 여부만 최종 확인 부탁드립니다.서드파티 SDK가 자체 암호화(예: E2E/키관리)를 제공하지 않고, 네트워크 암호화가 TLS 수준이라면 NO가 일반적입니다. 기능적으로 자체/커스텀 암호화가 있다면 YES로 전환 필요합니다.
Also applies to: 495-495
Poppool/CoreLayer/Infrastructure/Infrastructure/Service/KeyChainService.swift (1)
132-135: TokenType의 CaseIterable 추가 적절로그아웃/탈퇴 플로우에서
TokenType.allCases순회 사용이 가능해져 일관된 정리 로직에 도움됩니다. 하위 호환성에도 문제 없어 보입니다.Poppool/DataLayer/Data/Data/RepositoryImpl/SignUpRepositoryImpl.swift (1)
45-47: 디버그 출력 제거 LGTM불필요한
Poppool/PresentationLayer/SearchFeature/SearchFeature/Common/View/Component/TagCollectionHeaderView.swift (2)
15-17: PPUnderlinedTextButton 도입으로 커스텀 밑줄 제거 — 깔끔합니다.
중복 UI 제거와 디자인 시스템 일관성 측면에서 👍
15-17: Then 모듈 재수출 확인 필요
TagCollectionHeaderView.swift에서import Then없이.then을 사용하고 있습니다.Then이@_exported import Then등으로 재수출되어 이 파일에도 적용되는지 확인하고, 적용되지 않는다면 파일 상단에import Then을 추가해주세요.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/AgeSelectedModal/AgeSelectedView.swift (1)
11-12: DesignSystem API 마이그레이션 반영 — 좋습니다.
새 init 시그니처 사용이 일관되고 명확합니다.Also applies to: 20-21, 24-25
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/TermsDetail/TermsDetailController.swift (1)
26-29: UILabel.updateText(to:) 사용 — 일관성/속성 보존 측면에서 적절합니다.
기존 attributed 속성 보존 전략과 맞물려 안전합니다.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/TermsDetail/TermsDetailView.swift (1)
11-12: PPLabel 스타일 초기화 방식 통일 — 👍
디자인 시스템 스타일(.KOb15) 적용으로 유지보수성 향상.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift (1)
39-39: PPLabel 업데이트 방식 적합초기화 시점에는
attributedText가 없어updateText(to:)가 내부적으로text에 대입되어 부작용 없습니다. 현 변경은 타이포 시스템 이관 방향과 일치합니다.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteController.swift (1)
53-54: 닉네임 텍스트 업데이트 방식 적절
updateText(to:)사용으로 기존 스타일 유지 + 텍스트만 교체가 보장됩니다.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step2/SignUpStep2Controller.swift (3)
94-96: 라벨 스타일 보존형 업데이트 적용 적절
updateText(to:),updateTextColor(to:)로 스타일을 유지하며 변경되는 설명/색상만 반영합니다. 의도에 부합합니다.
103-105: 글자수 초과 처리 OK카운트 문구 업데이트와 10자 초과 시 시각적 피드백(shake) 처리 흐름이 명확합니다.
108-111: 상태별 색상 전환 로직 적절에러/경고 상태에만
.re500, 그 외.g500으로 일관성 있게 매핑되어 있습니다.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/AgeSelectedButton.swift (3)
6-6: Then 도입 일관성 OKStep 계열 뷰들과의 초기화 패턴 통일에 기여합니다.
17-19: PPLabel API 전환 및 토큰 컬러 적용 적절text-first 이니셜라이저+Then 체이닝으로 가독성/일관성 좋아졌습니다.
Also applies to: 27-29, 31-33
97-97: updateText 사용 타당attributedText 보존 흐름과 일치합니다. PPLabel이 초기 attributed 설정이 없다면 현재 구현도 문제 없습니다.
Poppool/PresentationLayer/DesignSystem/DesignSystem/Extension/UIButton+.swift (2)
8-10: setText 컬러 파라미터 추가 OKPPButton의 색상 일원화 전략과 일치합니다. 상태별 attributedTitle 적용도 적절합니다.
Also applies to: 21-23
5-10: 바이너리 소비자 없음 확인 레포 내에서 setText 호출은 모두 소스 의존이며, Pods 등 외부 모듈(사전 컴파일된 프레임워크)에서 해당 메서드 링크 사용이 발견되지 않았습니다.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/TagSection/TagSectionCell.swift (2)
12-12: PPLabel(style: .KOm13) 전환 적절디자인 시스템 폰트 토큰 일관성이 좋아졌습니다.
51-51: updateText / updateTextColor 적용 타당속성 유지 갱신 전략과 부합하며 선택/비선택 상태 전환이 명확합니다.
Also applies to: 55-55, 59-59
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpStep1View.swift (2)
11-16: 타이틀 레이블 이니셜라이저/Then 마이그레이션 좋습니다다국어 줄바꿈 고려(numberOfLines=0)도 적절합니다.
34-40: PPButton(buttonStyle:) 전환 및 초기 비활성 처리 LGTM버튼 상태 관리가 디자인 시스템과 정합합니다.
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPButton.swift (3)
79-81: 색상 인지 setText 적용 LGTM상태별 텍스트 컬러를 attributedTitle로 관리해 setTitleColor 의존성을 제거한 점이 좋습니다.
Also applies to: 106-108
55-64: Secondary 스타일 비활성 대비 색상 검토 필요
.secondary비활성 시disabledTextColor(.g50)과disabledBackgroundColor(.g50)가 동일해 텍스트가 보이지 않습니다. 디자인 토큰 확인 후 대비 확보 필요var disabledTextColor: UIColor { switch self { case .primary: return .g400 case .secondary: - return .g50 + return .g400 case .tertiary, .apple, .kakao: return .blu500 } }
71-72: disabledText 기본값 공백 변경 영향 검토 필요
PPButton(buttonStyle / style) 초기화 수십 곳에서 disabledText를 명시하지 않아 기본값인" "(한 칸 공백)이 적용됩니다. 비활성 상태에서 공백 문자가 의도치 않게 노출되거나 레이아웃에 영향을 주는지, setTitleColor 등 스타일 혼용 시 색상·상태 매핑이 올바른지 직접 검토 부탁드립니다.Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step3/View/SignUpStep3View.swift (2)
10-12: DesignSystem/Then 전환 깔끔합니다PPLabel/PPButton 신규 생성자와 Then 적용이 일관되고 명확합니다.
Also applies to: 29-34
10-12: 닉네임 라벨 색/스타일 보존 확인 요청setNickName에서 .text로 교체 시, PPLabel이 attributed 기반이면 스타일이 초기화될 수 있습니다. 다회 호출 시 색/스타일 유지되는지 확인 부탁드립니다(필요 시 updateText 계열 API 사용 검토).
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/SignUpComplete/SignUpCompleteView.swift (1)
17-30: 신규 라벨/버튼 생성자 적용 👍PPLabel(text:, style:), PPButton(buttonStyle:, text:)로 일관성 있게 정리되어 가독성이 좋아졌습니다.
Also applies to: 38-49
Poppool/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift (1)
11-13: DesignSystem/Then 적용 적절합니다닉네임 색상 처리 및 버튼 생성자 교체가 일관되고 명확합니다.
Also applies to: 40-45
Poppool/DataLayer/Data/Data/Network/Service/AppleLoginService.swift
Outdated
Show resolved
Hide resolved
Poppool/DataLayer/Data/Data/Network/Service/AppleLoginService.swift
Outdated
Show resolved
Hide resolved
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPUnderlinedTextButton.swift
Show resolved
Hide resolved
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/PPUnderlinedTextButton.swift
Show resolved
Hide resolved
...esentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpCheckBoxButton.swift
Show resolved
Hide resolved
...ol/PresentationLayer/Presentation/Presentation/Scene/SignUp/Step1/View/SignUpTermsView.swift
Show resolved
Hide resolved
...esentationLayer/Presentation/Presentation/Scene/SignUp/Step4/Main/View/SignUpStep4View.swift
Show resolved
Hide resolved
- 단일 컨트롤러 재사용시 상태 오류/중복 가능성 있음
- Then 사용중이어서 import문 추가 - trailing comma 제거
- RxRelay 사용중이어서 import문과 패키지 의존성 추가
* fix/#173: 버튼 활성시 텍스트 컬러가 적용되지 않던 문제 수정 * feat/#173: 밑줄 텍스트 버튼 공통 컴포넌트 구현 * feat/#173: 밑줄 텍스트 버튼 컴포넌트 구현 및 적용 * feat/#173: attr 텍스트 컬러 변경 메서드 구현 * fix/#173: 텍스트 스타일 적용 * fix/#173: 텍스트 스타일 적용 * feat/#173: 로그아웃, 회원탈퇴 시점에 키체인에서 토큰 제거 * refactor/#173: 키체인 실패 로그 추가 * fix/#173: 회원가입 후 바로 로그인되지 않던 문제 수정 * chore/#173: Xcode26 리퀴드글래서 효과 자동적용 제거 * fix/#173: 애플로그인 회원가입 화면 중복 이동 문제 해결 * refactor/#173: 애플로그인 코드 리팩터링 - 델리게이트의 생명주기를 메서드가 아닌 상위 클래스가 관리하도록 수정 - 인증 컨트롤러를 한번만 생성하도록 수정 * refactor/#173: ASAuthorizationController 인스턴스 재생성 - 단일 컨트롤러 재사용시 상태 오류/중복 가능성 있음 * fix/#173: PPUnderlined 컴포넌트 오류 수정 - Then 사용중이어서 import문 추가 - trailing comma 제거 * fix/#173: 컴포넌트 패키지 추가 오류 수정 - RxRelay 사용중이어서 import문과 패키지 의존성 추가
📌 이슈
✅ 작업 사항
Summary by CodeRabbit