-
Notifications
You must be signed in to change notification settings - Fork 3
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
Feat/Scrum 131 회원가입 도메인 & 프레젠테이션 로직 구현 #36
Feat/Scrum 131 회원가입 도메인 & 프레젠테이션 로직 구현 #36
Conversation
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.
추가적으로 수정이 필요한 부분 말씀해주시면 감사하겠습니다~!
let viewController = MainRootViewController() | ||
navigationController.pushViewController(viewController, animated: false) |
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.
사용하지 않는 코드라 제거했습니다.
public init(agree: Bool, title: String, agreement: String) { | ||
self.agree = agree | ||
self.title = title | ||
self.agreement = agreement | ||
} |
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.
사용처에서 initializer를 사용해야 해서 구현했습니다.
public var socialAccessToken: String? | ||
public var oAuthType: OAuthType? | ||
public var nickname: String? | ||
public var gender: GenderEntity? | ||
public var birthYear: String? | ||
public var terms: [Term] | ||
|
||
public init( | ||
socialAccessToken: String? = nil, | ||
oAuthType: OAuthType? = nil, | ||
nickname: String? = nil, | ||
gender: GenderEntity? = nil, | ||
birthYear: String? = nil, | ||
terms: [Term] = [] | ||
) { | ||
self.socialAccessToken = socialAccessToken | ||
self.oAuthType = oAuthType | ||
self.nickname = nickname | ||
self.gender = gender | ||
self.birthYear = birthYear | ||
self.terms = terms | ||
} |
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.
논의한 대로 Onboarding 화면 별로 나눠서 데이터를 이동시켜야 해서 Optional로 수정했습니다~!
private let navigationBar = NavigationBar() | ||
|
||
lazy var backButton: UIButton = { | ||
let button = UIButton() | ||
button.translatesAutoresizingMaskIntoConstraints = false | ||
|
||
let userInterfaceStyle = traitCollection.userInterfaceStyle | ||
let image = userInterfaceStyle == .light ? FeelinImages.backLight : FeelinImages.backDark | ||
button.setImage(image, for: .normal) | ||
|
||
return button | ||
}() | ||
|
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.
NavigationBar 뷰에 백버튼을 주입하는 방식으로 구현했습니다.
private lazy var nicknameTextField = FeelinLineInputField(placeholder: "닉네임") | ||
|
||
|
||
lazy var nicknameTextField = FeelinLineInputField(placeholder: "닉네임") |
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.
해당 Custom TextField의 TextField에 접근하기 위해 internal로 변경했습니다.
lazy var ageAgreeButton: UIButton = { | ||
return self.createAgreementButton("만 14세 이상 가입 동의") | ||
return self.createAgreementButton(TermEntity.ageAgree.title) | ||
}() | ||
|
||
lazy var serviceUsageAgreeButton: UIButton = { | ||
return self.createAgreementButton("서비스 이용약관 동의") | ||
return self.createAgreementButton(TermEntity.serviceUsage.title) | ||
}() | ||
|
||
lazy var personalInfoAgreeButton: UIButton = { | ||
return self.createAgreementButton("개인정보처리방침 동의") | ||
return self.createAgreementButton(TermEntity.personalInfo.title) | ||
}() | ||
|
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.
title, agreement에 대한 부분은 따로 관리가 필요하다고 생각해서 TermEntity를 구현해 각 title의 내용을 관리할 수 있도록 구현했습니다.
lazy var backButton: UIButton = { | ||
let button = UIButton() | ||
button.translatesAutoresizingMaskIntoConstraints = false | ||
|
||
let userInterfaceStyle = traitCollection.userInterfaceStyle | ||
let image = userInterfaceStyle == .light ? FeelinImages.backLight : FeelinImages.backDark | ||
button.setImage(image, for: .normal) | ||
|
||
return button | ||
}() | ||
|
||
let skipButton: UIButton = { | ||
let button = UIButton() | ||
button.translatesAutoresizingMaskIntoConstraints = false | ||
button.setTitle("건너뛰기", for: .normal) | ||
button.titleLabel?.font = SharedDesignSystemFontFamily.Pretendard.regular.font(size: 16) | ||
button.setTitleColor(Colors.gray05, for: .normal) | ||
return button | ||
}() |
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.
navigationBar에 backButton과 skipButton을 넣습니다.
flex.addItem(titleLabel) | ||
.marginTop(72) | ||
.marginTop(28) |
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.
네비게이션 바의 추가 구현으로 해당 값을 수정했ㅅ브니다.
profileCharacterCollectionView.publisher(for: [.didSelectItem, .didDeselectItem]) | ||
.sink { [weak self] indexPath in | ||
guard let self = self else { return } | ||
|
||
if let cell = self.profileCharacterCollectionView.cellForItem(at: indexPath) as? ProfileCharacterCell { | ||
let isSelected = self.profileCharacterCollectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false | ||
cell.setSelected(isSelected) | ||
selectButton.isEnabled = isSelected | ||
selectedProfileIndex = indexPath.row | ||
} | ||
} | ||
.store(in: &cancellables) |
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.
collectionView도 combine cocoa가 될 수 있도록 변경했습니다.
public var isValid: Bool = true { | ||
didSet { | ||
separator.backgroundColor = isValid ? Colors.gray02 : Colors.alertWarning | ||
} | ||
} |
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.
잘못 작성된 텍스트라면 다음 버튼이 활성화되지 않도록 하기 위해 해당 프로퍼티를 추가했습니다.
23f5b7b
to
b1b16a7
Compare
|
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.
고생하셨습니다. 몇 가지 코멘트 확인 요청드려요~
public final class NavigationBar: UIView { | ||
private let rootFlexContainer = UIView() | ||
private let leftBarView = UIView() | ||
private let titleView = UIView() | ||
private let rightBarView = UIView() | ||
|
||
public init() { | ||
super.init(frame: .zero) | ||
backgroundColor = Colors.background | ||
setupDefault() | ||
addUIComponents() | ||
} | ||
|
||
@available(*, unavailable) | ||
required init?(coder: NSCoder) { | ||
fatalError() | ||
} | ||
|
||
public override func layoutSubviews() { | ||
super.layoutSubviews() | ||
rootFlexContainer.pin.all() | ||
rootFlexContainer.flex.layout() | ||
} | ||
} | ||
|
||
public extension NavigationBar { | ||
func addTitleView(_ contentView: UIView) { | ||
titleView.flex.define { flex in | ||
flex.addItem(contentView).grow(1).shrink(1).marginHorizontal(5) | ||
} | ||
titleView.flex.layout() | ||
} | ||
|
||
func addLeftBarView(_ contentView: UIView) { | ||
leftBarView.flex.define { flex in | ||
flex.addItem(contentView).width(40).height(40) | ||
} | ||
leftBarView.flex.layout() | ||
} | ||
|
||
func addRightBarView(_ contentView: UIView) { | ||
rightBarView.flex.define { flex in | ||
flex.addItem(contentView).width(60).height(40) | ||
} | ||
rightBarView.flex.layout() | ||
} | ||
} | ||
|
||
private extension NavigationBar { | ||
func setupDefault() { | ||
rootFlexContainer.flex.direction(.row).alignItems(.center) | ||
leftBarView.flex.width(40).height(40) | ||
titleView.flex.grow(1).alignItems(.center).justifyContent(.center) | ||
rightBarView.flex.width(60).height(40) | ||
} | ||
|
||
func addUIComponents() { | ||
addSubview(rootFlexContainer) | ||
rootFlexContainer.flex.define { flex in | ||
flex.addItem(leftBarView) | ||
flex.addItem(titleView).grow(1).shrink(1) | ||
flex.addItem(rightBarView) | ||
} | ||
} | ||
} |
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.
고생하셨습니다~ 요긴하게 사용하겠네요~!!
LoginViewControllerDelegate, | ||
UseAgreementViewControllerDelegate, | ||
UserInformationViewControllerDelegate, | ||
ProfileViewControllerDelegate { | ||
public func showUseAgreementViewController(model: UserSignUpEntity) { | ||
let viewController = UseAgreementViewController(model: model) | ||
viewController.coordinator = self | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
||
public func showServiceUsageViewController() { | ||
|
||
} | ||
|
||
public func showPersonalInfoUsageViewController() { | ||
|
||
} | ||
|
||
public func showUserInformationViewController(model: UserSignUpEntity) { | ||
let viewController = UserInformationViewController(model: model) | ||
viewController.coordinator = self | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
||
public func showProfileViewController(model: UserSignUpEntity) { | ||
let viewController = ProfileViewController(model: model) | ||
viewController.coordinator = self | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
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.
네비게이션 같은 경우 show동사를 활용하는걸까요?
그렇다면 modal 같은 경우 어떤 동사를 활용해 볼 수 있을까요~?
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.
이 부분은 push~로 변경되어있습니다.
public extension UICollectionView { | ||
func publisher(for events: [PublisherEvent]) -> AnyPublisher<IndexPath, Never> { | ||
return CombineCollectionViewPublisher(collectionView: self, events: events) | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
enum PublisherEvent { | ||
case didSelectItem | ||
case didDeselectItem | ||
} | ||
} | ||
|
||
private struct CombineCollectionViewPublisher: Publisher { | ||
typealias Output = IndexPath | ||
typealias Failure = Never | ||
|
||
private let collectionView: UICollectionView | ||
private let events: [UICollectionView.PublisherEvent] | ||
|
||
init(collectionView: UICollectionView, events: [UICollectionView.PublisherEvent]) { | ||
self.collectionView = collectionView | ||
self.events = events | ||
} | ||
|
||
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, IndexPath == S.Input { | ||
let subscription = CombineCollectionViewSubscription( | ||
subscriber: subscriber, | ||
collectionView: collectionView, | ||
events: events | ||
) | ||
subscriber.receive(subscription: subscription) | ||
} | ||
} | ||
|
||
private final class CombineCollectionViewSubscription<S: Subscriber>: NSObject, Subscription, UICollectionViewDelegate where S.Input == IndexPath, S.Failure == Never { | ||
private var subscriber: S? | ||
private weak var collectionView: UICollectionView? | ||
private let events: [UICollectionView.PublisherEvent] | ||
|
||
init( | ||
subscriber: S, | ||
collectionView: UICollectionView, | ||
events: [UICollectionView.PublisherEvent] | ||
) { | ||
self.subscriber = subscriber | ||
self.collectionView = collectionView | ||
self.events = events | ||
super.init() | ||
self.collectionView?.delegate = self | ||
} | ||
|
||
/// Subscribers.Demand는 Combine의 백프레셔(Backpressure)를 제어하기 위한 것 | ||
/// 여기서는 기본적으로 무시하고 이벤트가 발생할 때마다 전송하는 방식으로 구현 | ||
func request(_ demand: Subscribers.Demand) { } | ||
|
||
func cancel() { | ||
subscriber = nil | ||
} | ||
|
||
func collectionView( | ||
_ collectionView: UICollectionView, | ||
didSelectItemAt indexPath: IndexPath | ||
) { | ||
if events.contains(.didSelectItem) { | ||
_ = subscriber?.receive(indexPath) | ||
} | ||
} | ||
|
||
func collectionView( | ||
_ collectionView: UICollectionView, | ||
didDeselectItemAt indexPath: IndexPath | ||
) { | ||
if events.contains(.didDeselectItem) { | ||
_ = subscriber?.receive(indexPath) | ||
} | ||
} | ||
} |
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.
lgtm~~👍👍👍
public override var isSelected: Bool { | ||
didSet { | ||
updateImage() | ||
} | ||
} | ||
|
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.
맞습니다~ isChecked는 불필요한 값이었어요 ㅎㅎ
let mockData = """ | ||
{ | ||
"status": "회원가입이 완료되었습니다.", | ||
"data": { | ||
"accessToken": "\(expectedAccessToken)", | ||
"refreshToken": "\(expectedRefreshToken)" | ||
} | ||
} | ||
""".data(using: .utf8)! |
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.
태현님, 요거 모델 수정해야할듯 해요!! 저희 status는 안받기로 합의된걸로 기억합니당~
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.
수정했습니다.
let profileImagePublisher = CurrentValueSubject<String, Never>(ProfileCharacterType.defaultCharacter) | ||
|
||
nicknameTextFieldPublisher | ||
.sink { [weak self] isEnabled in | ||
guard let self = self else { return } | ||
nextButton.isEnabled = isEnabled && nicknameTextField.isValid | ||
profileSelectionPublisher | ||
.sink { character in | ||
profileImagePublisher.send(character) | ||
} | ||
.store(in: &cancellables) | ||
|
||
let nextButtonPublisher = nextButton.publisher(for: .touchUpInside) | ||
.eraseToAnyPublisher() | ||
|
||
let combinedPublisher = nextButtonPublisher | ||
.combineLatest(combinedSignUpModelPublisher) | ||
.map { $1 } | ||
.eraseToAnyPublisher() | ||
|
||
let input = ProfileViewModel.Input( | ||
combinedSignUpModelPublisher: combinedPublisher | ||
nicknameTextPublisher: nicknameTextPublisher.eraseToAnyPublisher(), | ||
profileImagePublisher: profileImagePublisher.eraseToAnyPublisher(), | ||
nextButtonTapPublisher: nextButton.publisher(for: .touchUpInside).eraseToAnyPublisher() | ||
) |
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.
디코로 말씀드린거 같이 하나의 publisher를 input에 주입하는 방향으로 수정하셔도 좋을 듯 해요~ p3
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.
수정했습니다~!
func convertProfileImage(input: Input) -> AnyPublisher<UIImage, Never> { | ||
input.profileImagePublisher | ||
.map { [weak self] profileCharacter in | ||
guard let image = ProfileCharacterType(rawValue: profileCharacter)?.image else { | ||
self?.profileImageErrorPublisher.send(profileCharacter) | ||
return UIImage() | ||
} | ||
return image | ||
} | ||
.eraseToAnyPublisher() | ||
} |
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.
요거 image는 optional이어도 될듯 한데 어떻게 생각하시나요~ p3
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.
좋습니다~! 수정했습니다.
.compactMap { (nickname, profileCharacter) -> UserSignUpEntity? in | ||
guard let nickname = nickname, !nickname.isEmpty, nickname.count < 10 else { return nil } | ||
return UserSignUpEntity(nickname: nickname, profileCharacter: profileCharacter) | ||
} | ||
.eraseToAnyPublisher() |
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.
compactMap을 통해 optional한 값이 방출되는게 조금 마음에 걸립니다...ㅎ
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.
수정했습니다.
return input.nextButtonTapPublisher | ||
.combineLatest(combinedSignUpModelPublisher) | ||
.flatMap { (tap, entity) in | ||
return Just(SignUpResult.success).eraseToAnyPublisher() | ||
} | ||
.eraseToAnyPublisher() |
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.
CombineLatest 중첩인데 작동 잘 되는지 확인 요청 드려요~ p2
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.
확인 결과 잘 작동합니다. zip으로 변경 시 nickname을 최신 값으로 가져올 수 없어 combineLatest 중첩이 필요합니다.
혹시 다른 방법이 있을까요..?
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.
아닙니다~~ 확인차 코멘트 드린거에요. 잘 작동한다면 굳이 다른 방법을 찾지 않아도 될듯 합니다~
} | ||
|
||
private let signUpUseCase: SignUpUseCase | ||
private var userSignUpEntity: UserSignUpEntity | ||
private var profileImageErrorPublisher = PassthroughSubject<String, Never>() |
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.
profileImageErrorPublisher가 꼭 필요한가 싶기는 합니다. api콜을 통해서 이미지를 가져오지 않기도 하고 이미지 갯수가 많은 것도 아니어서요~! p4
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.
제거했습니다.
e4e8cf5
to
4988fa4
Compare
- UICollectionView didSelect, didDeselectItemAt - UITextField
- coordinator 연동 (WelcomeViewController push 및 TabBarCoordinator로 이동)
- error로 없는 user일 경우 회원가입으로 가는 Flow - MockRecentLoginRecordService 추가 - accessToken, refreshToken 이름 수정
4988fa4
to
1fcf880
Compare
extension OnboardingCoordinator: CoordinatorDelegate, | ||
LoginViewControllerDelegate { | ||
LoginViewControllerDelegate, | ||
UseAgreementViewControllerDelegate, | ||
UserInformationViewControllerDelegate, | ||
ProfileViewControllerDelegate, | ||
WelcomeViewControllerDelegate { | ||
public func pushUseAgreementViewController(model: UserSignUpEntity) { | ||
let viewController = UseAgreementViewController(model: model) | ||
viewController.coordinator = self | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
||
public func pushServiceUsageViewController() { | ||
|
||
} | ||
|
||
public func pushPersonalInfoUsageViewController() { | ||
|
||
} | ||
|
||
public func pushUserInformationViewController(model: UserSignUpEntity) { | ||
let viewController = UserInformationViewController(model: model) | ||
viewController.coordinator = self | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
||
public func pushProfileViewController(model: UserSignUpEntity) { | ||
registerSignUpDependencies() | ||
let viewModel = profileDependencies(model: model) | ||
let viewController = ProfileViewController(viewModel: viewModel) | ||
viewController.coordinator = self | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
||
public func pushWelcomeViewController() { | ||
let viewController = WelcomeViewController() | ||
viewController.coordinator = self | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
||
public func connectTabBarFlow() { | ||
navigationController.popToRootViewController(animated: false) | ||
let onboardingCoordinator = TabBarCoordinator( | ||
navigationController: navigationController | ||
) | ||
|
||
onboardingCoordinator.delegate = self | ||
onboardingCoordinator.start() | ||
childCoordinators.append(onboardingCoordinator) | ||
} | ||
|
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.
👍👍
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.
확인했습니다~ input & output로직도 공들이신만큼 개선되어보입니다. 머지하셔도 좋습니다~ 얼른 다음 feature로 넘어가시죠
* docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * remove: Feature Onboarding View 파일 폴더링 * feat<SharedDesignSystem>: NavigationBar View 구현 * feat<SharedUtil>: CocoaCombine 추가 구현 - UICollectionView didSelect, didDeselectItemAt - UITextField * chore: 불필요한 개행 및 코드 제거 * refactor: FeelinAPI 부분 수정 * feat: Sign Up을 위한 Entity Model 구현 * refactor: Login ViewController 로그인 Error 타입 변경 * refactor: UserSignUpRequest init 추가 구현 * feat: UseAgreementVC binding 구현 및 Navigation Bar 추가 * feat: UserInformationVC binding 구현 및 Navigation Bar 추가 * refactor: ProfileVC binding 구현 및 navigation Bar 추가 * feat: Onboarding Coordinator 추가 구현 * refactor: UserSignUpRequest 프로퍼티 네이밍 변경 * refactor: 불필요한 추가 설정 제거 * refactor: 화면 전환 메서드 show -> push로 네이밍 변경 * feat: FeelinAPI signUp case 추가 * test: NetworkProvider SignUp 테스트 코드 구현 * refactor: 닉네임 텍스트 정규식 수정 * feat: Welcome View 구현 - coordinator 연동 (WelcomeViewController push 및 TabBarCoordinator로 이동) * refactor: Login Logic 수정 - error로 없는 user일 경우 회원가입으로 가는 Flow - MockRecentLoginRecordService 추가 - accessToken, refreshToken 이름 수정 * feat: UserSignUp UseCase 추가 - SignUpService - SignUpError - FeelinError - Request, Entity 객체 수정 * feat: ProfileViewModel 추가 * refactor: ProfileViewController에 ViewModel 주입 * refactor: 년생 데이터 "년" 문구 분리 * feat: ProfileViewController SignUp 로직 및 UI Interaction 추가 * refactor: 자동로그인 로직 수정 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * chore: 불필요한 개행 및 코드 제거 * refactor: UserSignUpRequest init 추가 구현 * refactor: UserSignUpRequest 프로퍼티 네이밍 변경 * feat: FeelinAPI signUp case 추가 * test: NetworkProvider SignUp 테스트 코드 구현 * refactor: Login Logic 수정 - error로 없는 user일 경우 회원가입으로 가는 Flow - MockRecentLoginRecordService 추가 - accessToken, refreshToken 이름 수정 * feat: UserSignUp UseCase 추가 - SignUpService - SignUpError - FeelinError - Request, Entity 객체 수정 * refactor: ProfileViewController에 ViewModel 주입 * chore: 불필요한 부분 제거 * refactor: Network feelinAPIError로 수정되어 userNotFound시 case로 변경 * refactor: SignUp API 실패시 Alert 추가 * refactor: Request 객체 프로퍼티 타입 변경 * refactor: 자동로그인 리턴 타입 수정 및 accessToken으로 변경 * refactor: 로그인 실패시 알림 문구 변경 * refactor: ProfileViewController Input, output 구조 변경 * refactor: Feelin Default Response 추가 * refactor: Error Code 추가 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * feat: FeelinAPI signUp case 추가 * test: NetworkProvider SignUp 테스트 코드 구현 * refactor: Login Logic 수정 - error로 없는 user일 경우 회원가입으로 가는 Flow - MockRecentLoginRecordService 추가 - accessToken, refreshToken 이름 수정 * feat: ProfileViewModel 추가 * refactor: ProfileViewController에 ViewModel 주입 * feat: ProfileViewController SignUp 로직 및 UI Interaction 추가 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * docs: issue templates 업데이트 * test: NetworkProvider SignUp 테스트 코드 구현 * refactor: Login Logic 수정 - error로 없는 user일 경우 회원가입으로 가는 Flow - MockRecentLoginRecordService 추가 - accessToken, refreshToken 이름 수정 * feat: ProfileViewModel 추가 * refactor: ProfileViewController에 ViewModel 주입 * feat: ProfileViewController SignUp 로직 및 UI Interaction 추가 * refactor: Network feelinAPIError로 수정되어 userNotFound시 case로 변경 * refactor: 로그인 실패시 알림 문구 변경 * refactor: ProfileViewController Input, output 구조 변경 * refactor: Error Code 추가 * chore: 불필요한 부분 제거 * refactor: 불필요한 Publisher 제거 및 수정 * refactor: FeelinDefalutResponse 프로퍼티 네임 변경 * refactor: Profile Image Optional하게 받을 수 있도록 변경 및 불필요한 코드 제거 * refactor: NetworkProviderTests Mock Data 변경 * refactor: 회원가입 데이터 로직 수정 * chore: 파라미터 빈값 처리 * chore: 불필요한 코드 제거 * refactor: Profile ViewModel Sign up 코드 수정
PR 타입
어떤 변경에 대한 PR인가요?
배경
목표
결과 (Optional)
질문