diff --git a/Projects/CommonUI/Sources/Component/LMTextField.swift b/Projects/CommonUI/Sources/Component/LMTextField.swift index 8ff54c3..6bc57c2 100644 --- a/Projects/CommonUI/Sources/Component/LMTextField.swift +++ b/Projects/CommonUI/Sources/Component/LMTextField.swift @@ -52,4 +52,8 @@ public class LMTextField: UITextField { $0.height.equalTo(55) } } + + public func currentText() -> String { + return self.text ?? "" + } } diff --git a/Projects/CommonUI/Sources/View/Login/SignInView.swift b/Projects/CommonUI/Sources/View/Login/SignInView.swift index b986f98..70deb08 100644 --- a/Projects/CommonUI/Sources/View/Login/SignInView.swift +++ b/Projects/CommonUI/Sources/View/Login/SignInView.swift @@ -19,12 +19,13 @@ open class SignInView: UIView { } var textFieldStackView = UIStackView() - var idTextField = LMTextField() - var passwordTextField = LMTextField() - var loginButton = LMButton(textColor: CommonUIAssets.LMBlack, + public var idTextField = LMTextField() + public var passwordTextField = LMTextField() + var signInButton = LMButton(textColor: CommonUIAssets.LMBlack, bgColor: CommonUIAssets.LMOrange1) var signUpButton = UIButton() + public let signInTapped = PublishRelay() public let signUpTapped = PublishRelay() private let disposeBag = DisposeBag() @@ -40,6 +41,10 @@ open class SignInView: UIView { } func bindEvents() { + signInButton.rx.tap + .bind(to: signInTapped) + .disposed(by: disposeBag) + signUpButton.rx.tap .bind(to: signUpTapped) .disposed(by: disposeBag) @@ -63,7 +68,7 @@ open class SignInView: UIView { $0.isSecureTextEntry = true } - loginButton = loginButton.then { + signInButton = signInButton.then { $0.setTitle("๋กœ๊ทธ์ธ", for: .normal) } @@ -84,7 +89,7 @@ open class SignInView: UIView { } func initUI() { - [logoView, textFieldStackView, loginButton, signUpButton] + [logoView, textFieldStackView, signInButton, signUpButton] .forEach { addSubview($0) } [idTextField, passwordTextField] @@ -102,14 +107,14 @@ open class SignInView: UIView { $0.height.equalTo(130) } - loginButton.snp.makeConstraints { + signInButton.snp.makeConstraints { $0.top.equalTo(textFieldStackView.snp.bottom).offset(33) $0.centerX.equalToSuperview() $0.horizontalEdges.equalToSuperview().inset(20) } signUpButton.snp.makeConstraints { - $0.top.equalTo(loginButton.snp.bottom).offset(35) + $0.top.equalTo(signInButton.snp.bottom).offset(35) $0.centerX.equalToSuperview() $0.height.equalTo(27) } diff --git a/Projects/Data/Sources/DTO/LoginDTO.swift b/Projects/Data/Sources/DTO/LoginDTO.swift index 50d10a5..8904d3a 100644 --- a/Projects/Data/Sources/DTO/LoginDTO.swift +++ b/Projects/Data/Sources/DTO/LoginDTO.swift @@ -9,10 +9,29 @@ import Foundation import Domain public struct LoginDTO: Decodable { + public let is_success: Bool + public let code: String + public let message: String + public let data: LoginDataDTO +} + +public struct LoginDataDTO: Decodable { public let accessToken: String + public let refreshToken: String } extension LoginDTO { + func getMessage() -> LoginVO { + return .init(accessToken: data.accessToken, + refreshToken: data.refreshToken) + } +} + +public struct GoogleLoginDTO: Decodable { + public let accessToken: String +} + +extension GoogleLoginDTO { // func toDomain() -> CourseVO { // return CourseVO(courseLv: 1, // courseDescription: "courseDescription", diff --git a/Projects/Data/Sources/Repository/LoginRepository.swift b/Projects/Data/Sources/Repository/LoginRepository.swift index d02a447..26f7786 100644 --- a/Projects/Data/Sources/Repository/LoginRepository.swift +++ b/Projects/Data/Sources/Repository/LoginRepository.swift @@ -13,17 +13,17 @@ import Foundation public class DefaultLoginRepository: LoginRepository { public init() {} - public func postGoogleLogin() -> Single { + public func postGoogleLogin() -> Single { return request( endpoint: "/oauth2/authorization/google", - responseType: LoginDTO.self + responseType: GoogleLoginDTO.self ) .map { dto in - return LoginVO(accessToken: "") + return GoogleLoginVO(accessToken: "") } } - public func postAppleLogin(userName: String?, identityToken: String) -> Single { + public func postAppleLogin(userName: String?, identityToken: String) -> Single { let params: Parameters = [ "userName": userName, "identityToken": identityToken @@ -52,7 +52,7 @@ public class DefaultLoginRepository: LoginRepository { let components = URLComponents(string: location) { let items = components.queryItems ?? [] let accessToken = items.first(where: { $0.name == "accessToken" })?.value - single(.success(LoginVO(accessToken: accessToken))) + single(.success(GoogleLoginVO(accessToken: accessToken))) } else { let error = NSError(domain: "DefaultLoginRepository", code: -1, diff --git a/Projects/Data/Sources/Repository/SignRepository.swift b/Projects/Data/Sources/Repository/SignRepository.swift index 123bb61..d54b8b2 100644 --- a/Projects/Data/Sources/Repository/SignRepository.swift +++ b/Projects/Data/Sources/Repository/SignRepository.swift @@ -13,6 +13,17 @@ public class DefaultSignRepository: SignRepository { public init() { } + public func postSignIn(email: String, password: String) -> Single { + let params = ["email": email, + "password": password] + return request(endpoint: "/api/auth/sign-in", + parameters: params, + responseType: LoginDTO.self) + .map { dto in + return dto.getMessage() + } + } + public func postEmail(email: String) -> Single { let params = ["email": email] return request(endpoint: "/api/auth/email", @@ -24,7 +35,8 @@ public class DefaultSignRepository: SignRepository { } public func postConfirm(email: String, code: String) -> Single { - let params = ["email": email, "code": code] + let params = ["email": email, + "code": code] return request(endpoint: "/api/auth/email/confirm", parameters: params, responseType: DefaultDTO.self) @@ -34,7 +46,9 @@ public class DefaultSignRepository: SignRepository { } public func postSignUp(username: String, email: String, password: String) -> Single { - let params = ["username": username, "email": email, "password": password] + let params = ["username": username, + "email": email, + "password": password] return request(endpoint: "/api/auth/sign-up", parameters: params, responseType: DefaultDTO.self) diff --git a/Projects/Data/Sources/Repository/TokenRepository.swift b/Projects/Data/Sources/Repository/TokenRepository.swift index a071d9f..59438cd 100644 --- a/Projects/Data/Sources/Repository/TokenRepository.swift +++ b/Projects/Data/Sources/Repository/TokenRepository.swift @@ -10,23 +10,38 @@ import Foundation public class DefaultTokenRepository: TokenRepository { private let userDefaults = UserDefaults.standard - private let accessTokenKey = "accessToken" + private let accessToken = "accessToken" + private let refreshToken = "refreshToken" public init() { // Initialization if needed } public func saveAccessToken(_ token: String) { - userDefaults.set(token, forKey: accessTokenKey) + userDefaults.set(token, forKey: accessToken) print("โœ… Access Token saved: \(token)") } public func getAccessToken() -> String? { - return userDefaults.string(forKey: accessTokenKey) + return userDefaults.string(forKey: accessToken) } public func clearAccessToken() { - userDefaults.removeObject(forKey: accessTokenKey) + userDefaults.removeObject(forKey: accessToken) print("๐Ÿ—‘๏ธ Access Token cleared") } + + public func saveRefreshToken(token: String) { + userDefaults.set(token, forKey: refreshToken) + print("โœ… Refresh Token saved: \(token)") + } + + public func getRefreshToken() -> String? { + return userDefaults.string(forKey: refreshToken) + } + + public func clearRefreshToken() { + userDefaults.removeObject(forKey: refreshToken) + print("๐Ÿ—‘๏ธ Refresh Token cleared") + } } diff --git a/Projects/Domain/Sources/RepositoryProtocol/LoginRepository.swift b/Projects/Domain/Sources/RepositoryProtocol/LoginRepository.swift index 34607d1..1a7560a 100644 --- a/Projects/Domain/Sources/RepositoryProtocol/LoginRepository.swift +++ b/Projects/Domain/Sources/RepositoryProtocol/LoginRepository.swift @@ -8,6 +8,6 @@ import RxSwift public protocol LoginRepository { - func postGoogleLogin() -> Single - func postAppleLogin(userName: String?, identityToken: String) -> Single + func postGoogleLogin() -> Single + func postAppleLogin(userName: String?, identityToken: String) -> Single } diff --git a/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift index 8f231b5..4eda6b7 100644 --- a/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift +++ b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift @@ -8,6 +8,7 @@ import RxSwift public protocol SignRepository { + func postSignIn(email: String, password: String) -> Single func postEmail(email: String) -> Single func postConfirm(email: String, code: String) -> Single func postSignUp(username: String, email: String, password: String) -> Single diff --git a/Projects/Domain/Sources/RepositoryProtocol/TokenRepository.swift b/Projects/Domain/Sources/RepositoryProtocol/TokenRepository.swift index a80d27b..19a27af 100644 --- a/Projects/Domain/Sources/RepositoryProtocol/TokenRepository.swift +++ b/Projects/Domain/Sources/RepositoryProtocol/TokenRepository.swift @@ -8,7 +8,10 @@ import RxSwift public protocol TokenRepository { - func saveAccessToken(_ token: String) + func saveAccessToken(token: String) func getAccessToken() -> String? func clearAccessToken() + func saveRefreshToken(token: String) + func getRefreshToken() -> String? + func clearRefreshToken() } diff --git a/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift b/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift index 77625bf..cde1ec8 100644 --- a/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift +++ b/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift @@ -8,8 +8,8 @@ import RxSwift public protocol LoginUseCase { - func postGoogleLogin() -> Single - func postAppleLogin(userName: String?, identityToken: String) -> Single + func postGoogleLogin() -> Single + func postAppleLogin(userName: String?, identityToken: String) -> Single } public final class DefaultLoginUseCase: LoginUseCase { @@ -19,11 +19,11 @@ public final class DefaultLoginUseCase: LoginUseCase { self.repository = repository } - public func postGoogleLogin() -> Single { + public func postGoogleLogin() -> Single { return repository.postGoogleLogin() } - public func postAppleLogin(userName: String?, identityToken: String) -> Single { + public func postAppleLogin(userName: String?, identityToken: String) -> Single { return repository.postAppleLogin(userName: userName, identityToken: identityToken) } } diff --git a/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift index d47e6a2..cf44439 100644 --- a/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift +++ b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift @@ -8,6 +8,7 @@ import RxSwift public protocol SignUseCase { + func postSignIn(email: String, password: String) -> Single func postEmail(email: String) -> Single func postConfirm(email: String, code: String) -> Single func postSignUp(username: String, email: String, password: String) -> Single @@ -20,6 +21,10 @@ public final class DefaultSignUseCase: SignUseCase { self.repository = repository } + public func postSignIn(email: String, password: String) -> Single { + return repository.postSignIn(email: email, password: password) + } + public func postEmail(email: String) -> Single { return repository.postEmail(email: email) } diff --git a/Projects/Domain/Sources/VO/LoginVO.swift b/Projects/Domain/Sources/VO/LoginVO.swift index 6853246..3a042d1 100644 --- a/Projects/Domain/Sources/VO/LoginVO.swift +++ b/Projects/Domain/Sources/VO/LoginVO.swift @@ -5,16 +5,19 @@ // Created by ๋ฐ•์ง€์œค on 7/16/25. // -//public struct LoginVO { -// public let list: [CourseVO] -// -// public init(list: [CourseVO]) { -// self.list = list -// } -//} - public struct LoginVO { public let accessToken: String? + public let refreshToken: String? + + public init(accessToken: String?, + refreshToken: String?) { + self.accessToken = accessToken + self.refreshToken = refreshToken + } +} + +public struct GoogleLoginVO { + public let accessToken: String? public init(accessToken: String?) { self.accessToken = accessToken diff --git a/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift b/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift index f92d587..1e04d7c 100644 --- a/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift +++ b/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift @@ -15,6 +15,7 @@ protocol AppCoordinator: Coordinator { func showTabbarFlow() func setTabBarCoordinator() func getChildCoordinator(_ type: CoordinatorType) -> Coordinator? + func showHomeAfterLogin() } final class DefaultAppCoordinator: AppCoordinator{ @@ -44,6 +45,10 @@ final class DefaultAppCoordinator: AppCoordinator{ let signUpViewController = self.dependency.injector.resolve(SignUpViewController.self) self.navigationController.pushViewController(signUpViewController, animated: true) } + signInViewController.onLoginSuccess = { [weak self] in + guard let self else { return } + self.showHomeAfterLogin() + } self.navigationController.pushViewController(signInViewController, animated: true) } self.navigationController.pushViewController(loginViewController, animated: true) @@ -59,6 +64,13 @@ final class DefaultAppCoordinator: AppCoordinator{ tabBarCoordinator.start() } + /// ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ํ™ˆ์œผ๋กœ ์ด๋™ + func showHomeAfterLogin() { + // ๋กœ๊ทธ์ธ ๊ด€๋ จ ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ๋“ค์„ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๊ณ  ํƒญ๋ฐ”๋กœ ์ด๋™ + navigationController.viewControllers.removeAll() + showTabbarFlow() + } + /// ํƒญ๋ฐ” ์ปจํŠธ๋กค๋Ÿฌ ์„ธํŒ…, ์ž์‹ ์ฝ”๋””๋„ค์ดํ„ฐ๋กœ ๋“ฑ๋ก func setTabBarCoordinator() { let dependency = DefaultTabBarController.Dependency.init( diff --git a/Projects/LearnMate/Sources/DI/LoginAssembly.swift b/Projects/LearnMate/Sources/DI/LoginAssembly.swift index f3981cc..e09a32d 100644 --- a/Projects/LearnMate/Sources/DI/LoginAssembly.swift +++ b/Projects/LearnMate/Sources/DI/LoginAssembly.swift @@ -28,7 +28,8 @@ public struct LoginAssembly: Assembly { /// Sign DI ๋“ฑ๋ก container.register(SignViewModel.self) { resolver in let useCase = resolver.resolve(SignUseCase.self)! - return SignViewModel(signUseCase: useCase) + let tokenRepository = resolver.resolve(TokenRepository.self)! + return SignViewModel(signUseCase: useCase, tokenRepository: tokenRepository) } container.register(SignInViewController.self) { resolver in diff --git a/Projects/Login/Sources/View/SignInViewController.swift b/Projects/Login/Sources/View/SignInViewController.swift index e6c5871..871bf97 100644 --- a/Projects/Login/Sources/View/SignInViewController.swift +++ b/Projects/Login/Sources/View/SignInViewController.swift @@ -20,6 +20,7 @@ public class SignInViewController: BaseViewController { let signInView = SignInView() public var onPresentSignUp: (() -> Void)? + public var onLoginSuccess: (() -> Void)? public init(signViewModel: SignViewModel) { self.viewModel = signViewModel @@ -32,7 +33,6 @@ public class SignInViewController: BaseViewController { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) -// self.navigationController?.setNavigationBarHidden(, animated: false) } public override func viewDidLoad() { @@ -42,6 +42,17 @@ public class SignInViewController: BaseViewController { } private func bindActions() { + signInView.signInTapped + .bind { [weak self] in + guard let self = self else { return } + + let email = self.signInView.idTextField.currentText() + let password = self.signInView.passwordTextField.currentText() + + self.postSignIn(email: email, password: password) + } + .disposed(by: disposeBag) + signInView.signUpTapped .bind { [weak self] in self?.presentSignUp() @@ -49,13 +60,21 @@ public class SignInViewController: BaseViewController { .disposed(by: disposeBag) } + private func postSignIn(email: String, password: String) { + self.viewModel.postSignIn(email: email, + password: password) + } + private func presentSignUp() { onPresentSignUp?() -// let signUpViewController = SignUpViewController(signViewModel: viewModel) -// self.navigationController?.pushViewController(signUpViewController, animated: true) } private func bindTransition() { + viewModel.onSignInSuccess = { [weak self] in + DispatchQueue.main.async { + self?.onLoginSuccess?() + } + } } public override func setupViewProperty() { diff --git a/Projects/Login/Sources/ViewModel/SignViewModel.swift b/Projects/Login/Sources/ViewModel/SignViewModel.swift index 4c8cf55..35a4f59 100644 --- a/Projects/Login/Sources/ViewModel/SignViewModel.swift +++ b/Projects/Login/Sources/ViewModel/SignViewModel.swift @@ -10,6 +10,7 @@ import RxSwift import RxRelay protocol SignViewModelProtocol { + func postSignIn(email: String, password: String) func postEmail(email: String) func postConfirm(email: String, code: String) func postSignUp(username: String, email: String, password: String) @@ -18,15 +19,40 @@ protocol SignViewModelProtocol { public class SignViewModel: SignViewModelProtocol { private let disposeBag = DisposeBag() private let signUseCase: SignUseCase + private let tokenRepository: TokenRepository public weak var signInViewCoordinator: SignInCoordinator? public weak var signUpViewCoordinator: SignUpCoordinator? public var onEmailSuccess: (() -> Void)? public var onConfirmSuccess: (() -> Void)? public var onConfirmFailure: (() -> Void)? + public var onSignInSuccess: (() -> Void)? public let emailVerified = BehaviorRelay(value: false) - public init(signUseCase: SignUseCase) { + public init(signUseCase: SignUseCase, tokenRepository: TokenRepository) { self.signUseCase = signUseCase + self.tokenRepository = tokenRepository + } + + func postSignIn(email: String, password: String) { + signUseCase.postSignIn(email: email, password: password) + .subscribe(onSuccess: { [weak self] response in + guard let self = self else { return } + print("๋กœ๊ทธ์ธ ์„ฑ๊ณต: \(response)") + + if let accessToken = response.accessToken { + self.tokenRepository.saveAccessToken(token: accessToken) + print("๐Ÿ”‘ ํ† ํฐ ์ €์žฅ ์™„๋ฃŒ: \(accessToken)") + } + if let refreshToken = response.refreshToken { + self.tokenRepository.saveRefreshToken(token: refreshToken) + print("๐Ÿ”„ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์ €์žฅ ์™„๋ฃŒ: \(refreshToken)") + } + + self.onSignInSuccess?() + }, onFailure: { error in + print("๋กœ๊ทธ์ธ ์‹คํŒจ: \(error)") + }) + .disposed(by: disposeBag) } func postEmail(email: String) {