From 635122f4a5abdd56836d6fdd613f92fc81d1be30 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Tue, 26 Aug 2025 03:02:16 +0900 Subject: [PATCH 01/16] feat: implement SignViewModel --- .../Login/Login.xcodeproj/project.pbxproj | 4 ++ .../Sources/ViewModel/SignViewModel.swift | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 Projects/Login/Sources/ViewModel/SignViewModel.swift diff --git a/Projects/Login/Login.xcodeproj/project.pbxproj b/Projects/Login/Login.xcodeproj/project.pbxproj index c1431fd..c399a8d 100644 --- a/Projects/Login/Login.xcodeproj/project.pbxproj +++ b/Projects/Login/Login.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 92D7E2A0C6D4E3A6F57BAFB0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C32E71FB3926A3A3118D0096 /* LoginViewModel.swift */; }; 950A0D5E2E5C3A6D00C07CF2 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D5D2E5C3A6700C07CF2 /* SignInViewController.swift */; }; 950A0D642E5C5F7D00C07CF2 /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D632E5C5F7900C07CF2 /* SignUpViewController.swift */; }; + 950A0D662E5CCB2F00C07CF2 /* SignViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D652E5CCB2900C07CF2 /* SignViewModel.swift */; }; 9543B5BD011752760C9E8D48 /* Common.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A3E03A11BBC654DEC110ECE /* Common.framework */; }; A2DD99D036B33B4B67E19670 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 149968626029AF0A6483A522 /* RxSwift.framework */; }; A60EB3815F8DEA7C7B2A1E4D /* SnapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A78DFB39E6F2ADF7C38CFA79 /* SnapKit.framework */; }; @@ -66,6 +67,7 @@ 7D44C290B1A433AEF98F1870 /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 950A0D5D2E5C3A6700C07CF2 /* SignInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewController.swift; sourceTree = ""; }; 950A0D632E5C5F7900C07CF2 /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; + 950A0D652E5CCB2900C07CF2 /* SignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignViewModel.swift; sourceTree = ""; }; 9A9C214589834256AC9DD6AB /* Then.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Then.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A6D197429DF4204F655968BA /* LoginTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoginTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A78DFB39E6F2ADF7C38CFA79 /* SnapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -145,6 +147,7 @@ isa = PBXGroup; children = ( C32E71FB3926A3A3118D0096 /* LoginViewModel.swift */, + 950A0D652E5CCB2900C07CF2 /* SignViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -270,6 +273,7 @@ files = ( 4027F4EA82D89F9520C87D34 /* LoginViewController.swift in Sources */, 92D7E2A0C6D4E3A6F57BAFB0 /* LoginViewModel.swift in Sources */, + 950A0D662E5CCB2F00C07CF2 /* SignViewModel.swift in Sources */, 950A0D642E5C5F7D00C07CF2 /* SignUpViewController.swift in Sources */, 950A0D5E2E5C3A6D00C07CF2 /* SignInViewController.swift in Sources */, ); diff --git a/Projects/Login/Sources/ViewModel/SignViewModel.swift b/Projects/Login/Sources/ViewModel/SignViewModel.swift new file mode 100644 index 0000000..07d0fc4 --- /dev/null +++ b/Projects/Login/Sources/ViewModel/SignViewModel.swift @@ -0,0 +1,46 @@ +// +// SignViewModel.swift +// Login +// +// Created by 박지윤 on 8/26/25. +// + +import Domain +import RxSwift + +protocol SignViewModelProtocol { + func postEmail(email: String) + func postConfirm(email: String, code: String) +} + +public class SignViewModel: SignViewModelProtocol { + private let disposeBag = DisposeBag() + private let signUseCase: SignUseCase + public init(signUseCase: SignUseCase) { + self.signUseCase = signUseCase + } + + func postEmail(email: String) { + signUseCase.postEmail(email: email) + .subscribe( + onCompleted: { + print("이메일 전송 성공") + }, + onError: { error in + print("이메일 전송 실패: \(error)") + } + ).disposed(by: disposeBag) + } + + func postConfirm(email: String, code: String) { + signUseCase.postConfirm(email: email, code: code) + .subscribe( + onCompleted: { + print("이메일 전송 성공") + }, + onError: { error in + print("이메일 전송 실패: \(error)") + } + ).disposed(by: disposeBag) + } +} From fdf6f2f46a083f18f8c2429838968c24d7511094 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Tue, 26 Aug 2025 03:02:42 +0900 Subject: [PATCH 02/16] feat: implement SignRepository --- Projects/Data/Data.xcodeproj/project.pbxproj | 13 +++-- .../Sources/Repository/SignRepository.swift | 49 +++++++++++++++++++ 2 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 Projects/Data/Sources/Repository/SignRepository.swift diff --git a/Projects/Data/Data.xcodeproj/project.pbxproj b/Projects/Data/Data.xcodeproj/project.pbxproj index 15e7bed..5a8cb7f 100644 --- a/Projects/Data/Data.xcodeproj/project.pbxproj +++ b/Projects/Data/Data.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -11,6 +11,7 @@ 43E9C2380F425520C1FA1AD2 /* CourseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAC2885ACFE998578DC25E8 /* CourseDTO.swift */; }; 684AAEA9796EED3F9FC592FC /* NetworkConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D46FD93B80D052843AD5063 /* NetworkConfiguration.swift */; }; 901ACA7B98089AB702ADA830 /* Domain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06FAA1459D11CCE724C34195 /* Domain.framework */; }; + 950A0D702E5CCF0200C07CF2 /* SignRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D6F2E5CCEFD00C07CF2 /* SignRepository.swift */; }; A334985695DC9388841BBC43 /* QuizRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCA951B89F2C3D20AA31F7F /* QuizRepository.swift */; }; B2F8FBFA915F696CCCA4152A /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3B0D3D8C7049B6856791C1D /* Alamofire.framework */; }; E1BFC73FB539432F6E12CD94 /* CourseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0730BC3657E24BCEA511A3C /* CourseRepository.swift */; }; @@ -41,6 +42,7 @@ 4E75197C294DE74F5162FAA7 /* Data.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Data.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5D46FD93B80D052843AD5063 /* NetworkConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConfiguration.swift; sourceTree = ""; }; 77810122262C6CB16D4D47DA /* QuizDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizDTO.swift; sourceTree = ""; }; + 950A0D6F2E5CCEFD00C07CF2 /* SignRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRepository.swift; sourceTree = ""; }; A3B0D3D8C7049B6856791C1D /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A44BC1E2E75FC256F832CA38 /* LoginDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginDTO.swift; sourceTree = ""; }; AAAC2885ACFE998578DC25E8 /* CourseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDTO.swift; sourceTree = ""; }; @@ -83,6 +85,7 @@ 73F3ED55BFDC2EF15878F5B6 /* Repository */ = { isa = PBXGroup; children = ( + 950A0D6F2E5CCEFD00C07CF2 /* SignRepository.swift */, B0730BC3657E24BCEA511A3C /* CourseRepository.swift */, 3D7624DE90CFBBEF778E120E /* LoginRepository.swift */, 3CCA951B89F2C3D20AA31F7F /* QuizRepository.swift */, @@ -172,8 +175,6 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - TargetAttributes = { - }; }; buildConfigurationList = BDB112DA78B2B42275EC3A62 /* Build configuration list for PBXProject "Data" */; compatibilityVersion = "Xcode 14.0"; @@ -212,6 +213,7 @@ FF43B3A4D0DC88307E918DB0 /* LoginDTO.swift in Sources */, E9463A3FF42D5F0960245F80 /* QuizDTO.swift in Sources */, 684AAEA9796EED3F9FC592FC /* NetworkConfiguration.swift in Sources */, + 950A0D702E5CCF0200C07CF2 /* SignRepository.swift in Sources */, E1BFC73FB539432F6E12CD94 /* CourseRepository.swift in Sources */, 34FD760EB97BB96E9D770BF0 /* LoginRepository.swift in Sources */, A334985695DC9388841BBC43 /* QuizRepository.swift in Sources */, @@ -245,10 +247,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - "$(inherited)", - DEBUG, - ); + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_COMPILATION_MODE = singlefile; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; diff --git a/Projects/Data/Sources/Repository/SignRepository.swift b/Projects/Data/Sources/Repository/SignRepository.swift new file mode 100644 index 0000000..844de04 --- /dev/null +++ b/Projects/Data/Sources/Repository/SignRepository.swift @@ -0,0 +1,49 @@ +// +// SignRepository.swift +// Data +// +// Created by 박지윤 on 8/26/25. +// + +import Domain +import RxSwift +import Alamofire + +public class DefaultSignRepository: SignRepository { + private let tokenRepository: TokenRepository + + public init(tokenRepository: TokenRepository) { + self.tokenRepository = tokenRepository + } + + public func postEmail(email: String) -> Completable { + return request(endpoint: "/api/auth/email") + } + + public func postConfirm(email: String, code: String) -> Completable { + return request(endpoint: "/api/auth/email/confirm") + } + + private func request(endpoint: String) -> Completable { + return Completable.create { completable in + let url = "\(NetworkConfiguration.baseUrl)\(endpoint)" + var headers: HTTPHeaders = [:] + let request = AF.request(url, + method: .get, + encoding: URLEncoding.queryString, + headers: headers) + .validate() + .response { response in + if let error = response.error { + print("❌ API 응답 실패") + completable(.error(error)) + } else { + print("✅ API 응답 성공") + completable(.completed) + } + } + + return Disposables.create { request.cancel() } + } + } +} From db1962b7782feb7faf4585627b884f6d3b08f0fd Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Tue, 26 Aug 2025 03:04:15 +0900 Subject: [PATCH 03/16] feat: implement SignUseCase & SignRepository --- .../Domain/Domain.xcodeproj/project.pbxproj | 17 +++++++++++ .../RepositoryProtocol/SignRepository.swift | 13 +++++++++ .../UseCase/{ => Login}/LoginUseCase.swift | 0 .../Sources/UseCase/Login/SignUseCase.swift | 29 +++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift rename Projects/Domain/Sources/UseCase/{ => Login}/LoginUseCase.swift (100%) create mode 100644 Projects/Domain/Sources/UseCase/Login/SignUseCase.swift diff --git a/Projects/Domain/Domain.xcodeproj/project.pbxproj b/Projects/Domain/Domain.xcodeproj/project.pbxproj index b51b4c5..1306090 100644 --- a/Projects/Domain/Domain.xcodeproj/project.pbxproj +++ b/Projects/Domain/Domain.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 69DAD609572D32F2BA3845AE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E1DA9A49E79A791F7A526A1 /* String+Extension.swift */; }; 92BD46EE48F6C63B6E43D069 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32AB908BB15CBDA006ADC3A1 /* UIView+Extension.swift */; }; 94174705336F0E353686EA83 /* LoginUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 574E8F32CEB080432AB7EA94 /* LoginUseCase.swift */; }; + 950A0D692E5CCBB900C07CF2 /* SignUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */; }; + 950A0D6B2E5CCBF000C07CF2 /* SignRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D6A2E5CCBE800C07CF2 /* SignRepository.swift */; }; 95369A7E2E28B8D9000C893F /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95369A7D2E28B8D1000C893F /* UIImage+Extension.swift */; }; A0CEC5A2E5A76EBD987CE37D /* StepVO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798CFC331192C9FE77F3446A /* StepVO.swift */; }; B0B0382EF5DFDACDD3EB8A75 /* QuizUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4565F6A02AFBF3D9A0D41B /* QuizUseCase.swift */; }; @@ -49,6 +51,8 @@ 747DBBCAB797E5E0A82177F2 /* CourseVO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseVO.swift; sourceTree = ""; }; 798CFC331192C9FE77F3446A /* StepVO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepVO.swift; sourceTree = ""; }; 8A2AB67DE6AA50B3229C7A86 /* LoginRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRepository.swift; sourceTree = ""; }; + 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUseCase.swift; sourceTree = ""; }; + 950A0D6A2E5CCBE800C07CF2 /* SignRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRepository.swift; sourceTree = ""; }; 95369A7D2E28B8D1000C893F /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 9A2E30672E510822AAF38EAD /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ABB8C62A6FE12783AD3819BE /* TokenUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenUseCase.swift; sourceTree = ""; }; @@ -112,6 +116,7 @@ 2BE278B80FDD95404961E580 /* UseCase */ = { isa = PBXGroup; children = ( + 950A0D672E5CCBAC00C07CF2 /* Login */, F904C836B5CD2D4DA5EAE38D /* CourseUseCase.swift */, 574E8F32CEB080432AB7EA94 /* LoginUseCase.swift */, 6F4565F6A02AFBF3D9A0D41B /* QuizUseCase.swift */, @@ -137,6 +142,15 @@ name = Project; sourceTree = ""; }; + 950A0D672E5CCBAC00C07CF2 /* Login */ = { + isa = PBXGroup; + children = ( + 574E8F32CEB080432AB7EA94 /* LoginUseCase.swift */, + 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */, + ); + path = Login; + sourceTree = ""; + }; 9C17CEB3595F4C3FA69C5BAF /* VO */ = { isa = PBXGroup; children = ( @@ -151,6 +165,7 @@ BC1A55AF7DC675AFEA6E7C12 /* RepositoryProtocol */ = { isa = PBXGroup; children = ( + 950A0D6A2E5CCBE800C07CF2 /* SignRepository.swift */, DCA3E06ED2A8180A63919B6C /* CourseRepository.swift */, 8A2AB67DE6AA50B3229C7A86 /* LoginRepository.swift */, 1BA1471CB01ECE73BAD3D595 /* QuizRepository.swift */, @@ -231,6 +246,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 950A0D6B2E5CCBF000C07CF2 /* SignRepository.swift in Sources */, 69DAD609572D32F2BA3845AE /* String+Extension.swift in Sources */, 92BD46EE48F6C63B6E43D069 /* UIView+Extension.swift in Sources */, D510BC17C4583615CB60439E /* CourseRepository.swift in Sources */, @@ -244,6 +260,7 @@ 5EB8590C9D2C002D0BDBC021 /* CourseVO.swift in Sources */, 0DED9075FE34124C093D9FDF /* LoginVO.swift in Sources */, 95369A7E2E28B8D9000C893F /* UIImage+Extension.swift in Sources */, + 950A0D692E5CCBB900C07CF2 /* SignUseCase.swift in Sources */, CFBF3D58082C5F201FCFFD5B /* QuizVO.swift in Sources */, A0CEC5A2E5A76EBD987CE37D /* StepVO.swift in Sources */, ); diff --git a/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift new file mode 100644 index 0000000..44fde6a --- /dev/null +++ b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift @@ -0,0 +1,13 @@ +// +// SignRepository.swift +// Domain +// +// Created by 박지윤 on 8/26/25. +// + +import RxSwift + +public protocol SignRepository { + func postEmail(email: String) -> Completable + func postConfirm(email: String, code: String) -> Completable +} diff --git a/Projects/Domain/Sources/UseCase/LoginUseCase.swift b/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift similarity index 100% rename from Projects/Domain/Sources/UseCase/LoginUseCase.swift rename to Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift diff --git a/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift new file mode 100644 index 0000000..a590fdd --- /dev/null +++ b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift @@ -0,0 +1,29 @@ +// +// SignUseCase.swift +// Domain +// +// Created by 박지윤 on 8/26/25. +// + +import RxSwift + +public protocol SignUseCase { + func postEmail(email: String) -> Completable + func postConfirm(email: String, code: String) -> Completable +} + +public final class DefaultSignUseCase: SignUseCase { + let repository: SignRepository + + public init(repository: SignRepository) { + self.repository = repository + } + + public func postEmail(email: String) -> Completable { + return repository.postEmail(email: email) + } + + public func postConfirm(email: String, code: String) -> Completable { + return repository.postConfirm(email: email, code: code) + } +} From 7503a2f85dff00f81198d6ed4390a9bb2ab8acbe Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Tue, 26 Aug 2025 03:06:40 +0900 Subject: [PATCH 04/16] feat: bind emailButton event --- Projects/CommonUI/Sources/Component/LMInputField.swift | 7 +++++++ Projects/CommonUI/Sources/View/Login/SignUpView.swift | 2 +- Projects/Login/Sources/View/SignInViewController.swift | 9 +++++---- Projects/Login/Sources/View/SignUpViewController.swift | 9 ++++++--- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Projects/CommonUI/Sources/Component/LMInputField.swift b/Projects/CommonUI/Sources/Component/LMInputField.swift index 777f13a..12ea4d1 100644 --- a/Projects/CommonUI/Sources/Component/LMInputField.swift +++ b/Projects/CommonUI/Sources/Component/LMInputField.swift @@ -9,6 +9,8 @@ import UIKit public class LMInputField: UIStackView { + public var onEmailButtonTapped: ((String) -> Void)? + public enum InputType { case email case password @@ -88,6 +90,7 @@ public class LMInputField: UIStackView { let emailButton = LMButton(textColor: CommonUIAssets.LMBlack, bgColor: CommonUIAssets.LMOrange1).then { $0.setTitle(buttonTitle, for: .normal) + $0.addTarget(self, action: #selector(authButtonTapped), for: .touchUpInside) } [inputTextField, emailButton] @@ -143,4 +146,8 @@ public class LMInputField: UIStackView { $0.width.equalToSuperview() } } + + @objc private func authButtonTapped() { + onEmailButtonTapped?(inputTextField.text ?? "") + } } diff --git a/Projects/CommonUI/Sources/View/Login/SignUpView.swift b/Projects/CommonUI/Sources/View/Login/SignUpView.swift index 9b47002..93a0418 100644 --- a/Projects/CommonUI/Sources/View/Login/SignUpView.swift +++ b/Projects/CommonUI/Sources/View/Login/SignUpView.swift @@ -17,7 +17,7 @@ open class SignUpView: UIView { let nameInputField = LMInputField(inputText: "이름", inputPlaceholder: "이름을 입력하세요", warningText: " 이름은 필수입니다") - let emailInputField = LMInputField(inputType: .email, + public let emailInputField = LMInputField(inputType: .email, inputText: "이메일", inputPlaceholder: " 이메일을 입력하세요", warningText: " 이메일 형식이 올바르지 않습니다", diff --git a/Projects/Login/Sources/View/SignInViewController.swift b/Projects/Login/Sources/View/SignInViewController.swift index f3f81b1..500b510 100644 --- a/Projects/Login/Sources/View/SignInViewController.swift +++ b/Projects/Login/Sources/View/SignInViewController.swift @@ -11,7 +11,8 @@ import SnapKit import RxSwift public class SignInViewController: BaseViewController { -// let viewModel: LoginViewModel + let signViewModel: SignViewModel + let navigationBar = DefaultNavigationBar(leftImage: CommonUIAssets.IconBack ?? nil, rightImage: nil, title: nil, @@ -19,8 +20,8 @@ public class SignInViewController: BaseViewController { let signInView = SignInView() - public override init() { -// self.viewModel = loginViewModel + public init(signViewModel: SignViewModel) { + self.signViewModel = signViewModel super.init() } @@ -48,7 +49,7 @@ public class SignInViewController: BaseViewController { } private func presentSignUp() { - let signUpViewController = SignUpViewController() + let signUpViewController = SignUpViewController(signViewModel: signViewModel) self.navigationController?.pushViewController(signUpViewController, animated: true) } diff --git a/Projects/Login/Sources/View/SignUpViewController.swift b/Projects/Login/Sources/View/SignUpViewController.swift index 50901a7..91b88de 100644 --- a/Projects/Login/Sources/View/SignUpViewController.swift +++ b/Projects/Login/Sources/View/SignUpViewController.swift @@ -11,7 +11,7 @@ import SnapKit import RxSwift public class SignUpViewController: BaseViewController { - // let viewModel: LoginViewModel + let signViewModel: SignViewModel let navigationBar = DefaultNavigationBar(leftImage: CommonUIAssets.IconBack ?? nil, rightImage: nil, title: "회원가입", @@ -25,8 +25,8 @@ public class SignUpViewController: BaseViewController { $0.setTitle("회원가입", for: .normal) } - public override init() { -// self.viewModel = loginViewModel + public init(signViewModel: SignViewModel) { + self.signViewModel = signViewModel super.init() } @@ -45,6 +45,9 @@ public class SignUpViewController: BaseViewController { } private func bindActions() { + signUpView.emailInputField.onEmailButtonTapped = { email in + self.signViewModel.postEmail(email: email) + } } private func bindTransition() { From fba068afe7c6a38140ac37ed9e6b53401f4ba4f1 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Tue, 26 Aug 2025 21:34:11 +0900 Subject: [PATCH 05/16] feat: implement LoginUseCase --- Projects/Domain/Domain.xcodeproj/project.pbxproj | 9 ++++----- Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Projects/Domain/Domain.xcodeproj/project.pbxproj b/Projects/Domain/Domain.xcodeproj/project.pbxproj index 1306090..8deaa4b 100644 --- a/Projects/Domain/Domain.xcodeproj/project.pbxproj +++ b/Projects/Domain/Domain.xcodeproj/project.pbxproj @@ -14,9 +14,9 @@ 5EB8590C9D2C002D0BDBC021 /* CourseVO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 747DBBCAB797E5E0A82177F2 /* CourseVO.swift */; }; 69DAD609572D32F2BA3845AE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E1DA9A49E79A791F7A526A1 /* String+Extension.swift */; }; 92BD46EE48F6C63B6E43D069 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32AB908BB15CBDA006ADC3A1 /* UIView+Extension.swift */; }; - 94174705336F0E353686EA83 /* LoginUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 574E8F32CEB080432AB7EA94 /* LoginUseCase.swift */; }; 950A0D692E5CCBB900C07CF2 /* SignUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */; }; 950A0D6B2E5CCBF000C07CF2 /* SignRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D6A2E5CCBE800C07CF2 /* SignRepository.swift */; }; + 950A0D822E5DE10C00C07CF2 /* LoginUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D812E5DE10700C07CF2 /* LoginUseCase.swift */; }; 95369A7E2E28B8D9000C893F /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95369A7D2E28B8D1000C893F /* UIImage+Extension.swift */; }; A0CEC5A2E5A76EBD987CE37D /* StepVO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798CFC331192C9FE77F3446A /* StepVO.swift */; }; B0B0382EF5DFDACDD3EB8A75 /* QuizUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4565F6A02AFBF3D9A0D41B /* QuizUseCase.swift */; }; @@ -46,13 +46,13 @@ 24F9958F3DAC8013B440CEEA /* QuizVO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizVO.swift; sourceTree = ""; }; 2E1DA9A49E79A791F7A526A1 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 32AB908BB15CBDA006ADC3A1 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; - 574E8F32CEB080432AB7EA94 /* LoginUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginUseCase.swift; sourceTree = ""; }; 6F4565F6A02AFBF3D9A0D41B /* QuizUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizUseCase.swift; sourceTree = ""; }; 747DBBCAB797E5E0A82177F2 /* CourseVO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseVO.swift; sourceTree = ""; }; 798CFC331192C9FE77F3446A /* StepVO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepVO.swift; sourceTree = ""; }; 8A2AB67DE6AA50B3229C7A86 /* LoginRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRepository.swift; sourceTree = ""; }; 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUseCase.swift; sourceTree = ""; }; 950A0D6A2E5CCBE800C07CF2 /* SignRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRepository.swift; sourceTree = ""; }; + 950A0D812E5DE10700C07CF2 /* LoginUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginUseCase.swift; sourceTree = ""; }; 95369A7D2E28B8D1000C893F /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 9A2E30672E510822AAF38EAD /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ABB8C62A6FE12783AD3819BE /* TokenUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenUseCase.swift; sourceTree = ""; }; @@ -118,7 +118,6 @@ children = ( 950A0D672E5CCBAC00C07CF2 /* Login */, F904C836B5CD2D4DA5EAE38D /* CourseUseCase.swift */, - 574E8F32CEB080432AB7EA94 /* LoginUseCase.swift */, 6F4565F6A02AFBF3D9A0D41B /* QuizUseCase.swift */, ABB8C62A6FE12783AD3819BE /* TokenUseCase.swift */, ); @@ -145,7 +144,7 @@ 950A0D672E5CCBAC00C07CF2 /* Login */ = { isa = PBXGroup; children = ( - 574E8F32CEB080432AB7EA94 /* LoginUseCase.swift */, + 950A0D812E5DE10700C07CF2 /* LoginUseCase.swift */, 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */, ); path = Login; @@ -248,13 +247,13 @@ files = ( 950A0D6B2E5CCBF000C07CF2 /* SignRepository.swift in Sources */, 69DAD609572D32F2BA3845AE /* String+Extension.swift in Sources */, + 950A0D822E5DE10C00C07CF2 /* LoginUseCase.swift in Sources */, 92BD46EE48F6C63B6E43D069 /* UIView+Extension.swift in Sources */, D510BC17C4583615CB60439E /* CourseRepository.swift in Sources */, D7012CC494E56CD8CE58176F /* LoginRepository.swift in Sources */, 1782D2A1A7FB2BD6FFA1DFEA /* QuizRepository.swift in Sources */, D81C7F2583A1BD30BD7C53DD /* TokenRepository.swift in Sources */, BBED9ACB62B271298F7028F8 /* CourseUseCase.swift in Sources */, - 94174705336F0E353686EA83 /* LoginUseCase.swift in Sources */, B0B0382EF5DFDACDD3EB8A75 /* QuizUseCase.swift in Sources */, 3E835D4F2E5F639557AC7C06 /* TokenUseCase.swift in Sources */, 5EB8590C9D2C002D0BDBC021 /* CourseVO.swift in Sources */, diff --git a/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift b/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift index aba0ba8..77625bf 100644 --- a/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift +++ b/Projects/Domain/Sources/UseCase/Login/LoginUseCase.swift @@ -2,7 +2,7 @@ // LoginUseCase.swift // Domain // -// Created by 박지윤 on 7/16/25. +// Created by 박지윤 on 8/26/25. // import RxSwift From 490a88aedd6907c02f2d1792d7037dcadcf14fc2 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Tue, 26 Aug 2025 23:11:29 +0900 Subject: [PATCH 06/16] feat: implement SignInCoordinator --- .../Sources/Coordinator/Coordinator.swift | 1 + .../Login/Login.xcodeproj/project.pbxproj | 12 +++++ .../Coordinator/SignInCoordinator.swift | 54 +++++++++++++++++++ .../Sources/View/SignInViewController.swift | 6 +-- 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 Projects/Login/Sources/Coordinator/SignInCoordinator.swift diff --git a/Projects/CommonUI/Sources/Coordinator/Coordinator.swift b/Projects/CommonUI/Sources/Coordinator/Coordinator.swift index 862361a..b2459f8 100644 --- a/Projects/CommonUI/Sources/Coordinator/Coordinator.swift +++ b/Projects/CommonUI/Sources/Coordinator/Coordinator.swift @@ -15,6 +15,7 @@ public enum CoordinatorType { case diary case stats case myPage + case signIn } public protocol Coordinator: AnyObject { diff --git a/Projects/Login/Login.xcodeproj/project.pbxproj b/Projects/Login/Login.xcodeproj/project.pbxproj index c399a8d..3ccee4e 100644 --- a/Projects/Login/Login.xcodeproj/project.pbxproj +++ b/Projects/Login/Login.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 950A0D5E2E5C3A6D00C07CF2 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D5D2E5C3A6700C07CF2 /* SignInViewController.swift */; }; 950A0D642E5C5F7D00C07CF2 /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D632E5C5F7900C07CF2 /* SignUpViewController.swift */; }; 950A0D662E5CCB2F00C07CF2 /* SignViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D652E5CCB2900C07CF2 /* SignViewModel.swift */; }; + 950A0D862E5DE6D000C07CF2 /* SignInCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D852E5DE6C600C07CF2 /* SignInCoordinator.swift */; }; 9543B5BD011752760C9E8D48 /* Common.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A3E03A11BBC654DEC110ECE /* Common.framework */; }; A2DD99D036B33B4B67E19670 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 149968626029AF0A6483A522 /* RxSwift.framework */; }; A60EB3815F8DEA7C7B2A1E4D /* SnapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A78DFB39E6F2ADF7C38CFA79 /* SnapKit.framework */; }; @@ -68,6 +69,7 @@ 950A0D5D2E5C3A6700C07CF2 /* SignInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewController.swift; sourceTree = ""; }; 950A0D632E5C5F7900C07CF2 /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; 950A0D652E5CCB2900C07CF2 /* SignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignViewModel.swift; sourceTree = ""; }; + 950A0D852E5DE6C600C07CF2 /* SignInCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInCoordinator.swift; sourceTree = ""; }; 9A9C214589834256AC9DD6AB /* Then.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Then.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A6D197429DF4204F655968BA /* LoginTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoginTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A78DFB39E6F2ADF7C38CFA79 /* SnapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -155,12 +157,21 @@ 82158BAC7310E834F5971A06 /* Sources */ = { isa = PBXGroup; children = ( + 950A0D842E5DE4F000C07CF2 /* Coordinator */, 04AE692FD1ACBC62C53BE327 /* View */, 787BEC2C2BCE59B03D483875 /* ViewModel */, ); path = Sources; sourceTree = ""; }; + 950A0D842E5DE4F000C07CF2 /* Coordinator */ = { + isa = PBXGroup; + children = ( + 950A0D852E5DE6C600C07CF2 /* SignInCoordinator.swift */, + ); + path = Coordinator; + sourceTree = ""; + }; A19BD00EC1C84FD92121B04F /* Project */ = { isa = PBXGroup; children = ( @@ -275,6 +286,7 @@ 92D7E2A0C6D4E3A6F57BAFB0 /* LoginViewModel.swift in Sources */, 950A0D662E5CCB2F00C07CF2 /* SignViewModel.swift in Sources */, 950A0D642E5C5F7D00C07CF2 /* SignUpViewController.swift in Sources */, + 950A0D862E5DE6D000C07CF2 /* SignInCoordinator.swift in Sources */, 950A0D5E2E5C3A6D00C07CF2 /* SignInViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Projects/Login/Sources/Coordinator/SignInCoordinator.swift b/Projects/Login/Sources/Coordinator/SignInCoordinator.swift new file mode 100644 index 0000000..48b3bc7 --- /dev/null +++ b/Projects/Login/Sources/Coordinator/SignInCoordinator.swift @@ -0,0 +1,54 @@ +// +// SignInCoordinator.swift +// Login +// +// Created by 박지윤 on 8/26/25. +// + +import CommonUI +import UIKit + +public protocol SignInCoordinator: Coordinator { + func showSignInFlow() +} + +public class DefaultSignInCoordinator: SignInCoordinator { + public struct Dependency { + let signInViewController: SignInViewController + let navigationController: UINavigationController + weak var finishDelegate: CoordinatorFinishDelegate? + + public init(signInViewController: SignInViewController, + navigationController: UINavigationController, + finishDelegate: CoordinatorFinishDelegate? = nil) { + self.signInViewController = signInViewController + self.navigationController = navigationController + self.finishDelegate = finishDelegate + } + } + + let dependency: Dependency + public var childCoordinators: [Coordinator] = [] + public var navigationController: UINavigationController + public var type: CoordinatorType = .signIn + public var finishDelegate: CoordinatorFinishDelegate? + + public init(dependency: Dependency) { + self.dependency = dependency + self.navigationController = dependency.navigationController + self.finishDelegate = dependency.finishDelegate + dependency.signInViewController.viewModel.signInViewCoordinator = self + } + + public func start() { + setNavigationBar() + showSignInFlow() + } + + func setNavigationBar() { + } + + public func showSignInFlow() { + navigationController.pushViewController(dependency.signInViewController, animated: true) + } +} diff --git a/Projects/Login/Sources/View/SignInViewController.swift b/Projects/Login/Sources/View/SignInViewController.swift index 500b510..c423cbb 100644 --- a/Projects/Login/Sources/View/SignInViewController.swift +++ b/Projects/Login/Sources/View/SignInViewController.swift @@ -11,7 +11,7 @@ import SnapKit import RxSwift public class SignInViewController: BaseViewController { - let signViewModel: SignViewModel + let viewModel: SignViewModel let navigationBar = DefaultNavigationBar(leftImage: CommonUIAssets.IconBack ?? nil, rightImage: nil, @@ -21,7 +21,7 @@ public class SignInViewController: BaseViewController { let signInView = SignInView() public init(signViewModel: SignViewModel) { - self.signViewModel = signViewModel + self.viewModel = signViewModel super.init() } @@ -49,7 +49,7 @@ public class SignInViewController: BaseViewController { } private func presentSignUp() { - let signUpViewController = SignUpViewController(signViewModel: signViewModel) + let signUpViewController = SignUpViewController(signViewModel: viewModel) self.navigationController?.pushViewController(signUpViewController, animated: true) } From fca6d88bab8c1f249ceb3356df8c473a7fb62c95 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Wed, 27 Aug 2025 00:22:35 +0900 Subject: [PATCH 07/16] feat: set DI Assembly --- Projects/LearnMate/Sources/DI/DataAssembly.swift | 4 ++++ Projects/LearnMate/Sources/DI/DomainAssembly.swift | 5 +++++ Projects/LearnMate/Sources/DI/LoginAssembly.swift | 11 +++++++++++ 3 files changed, 20 insertions(+) diff --git a/Projects/LearnMate/Sources/DI/DataAssembly.swift b/Projects/LearnMate/Sources/DI/DataAssembly.swift index 1e8aec7..2e969cf 100644 --- a/Projects/LearnMate/Sources/DI/DataAssembly.swift +++ b/Projects/LearnMate/Sources/DI/DataAssembly.swift @@ -28,5 +28,9 @@ public struct DataAssembly: Assembly { let tokenRepository = resolver.resolve(TokenRepository.self)! return DefaultQuizRepository(tokenRepository: tokenRepository) } + + container.register(SignRepository.self) { _ in + return DefaultSignRepository() + } } } diff --git a/Projects/LearnMate/Sources/DI/DomainAssembly.swift b/Projects/LearnMate/Sources/DI/DomainAssembly.swift index e7d7507..6fdb825 100644 --- a/Projects/LearnMate/Sources/DI/DomainAssembly.swift +++ b/Projects/LearnMate/Sources/DI/DomainAssembly.swift @@ -29,5 +29,10 @@ public struct DomainAssembly: Assembly { let repository = resolver.resolve(QuizRepository.self)! return DefaultQuizUseCase(repository: repository) } + + container.register(SignUseCase.self) { resolver in + let repository = resolver.resolve(SignRepository.self)! + return DefaultSignUseCase(repository: repository) + } } } diff --git a/Projects/LearnMate/Sources/DI/LoginAssembly.swift b/Projects/LearnMate/Sources/DI/LoginAssembly.swift index c72a932..793a018 100644 --- a/Projects/LearnMate/Sources/DI/LoginAssembly.swift +++ b/Projects/LearnMate/Sources/DI/LoginAssembly.swift @@ -24,5 +24,16 @@ public struct LoginAssembly: Assembly { let loginViewModel = resolver.resolve(LoginViewModel.self)! return LoginViewController(loginViewModel: loginViewModel) } + + /// Sign DI 등록 + container.register(SignViewModel.self) { resolver in + let useCase = resolver.resolve(SignUseCase.self)! + return SignViewModel(signUseCase: useCase) + } + + container.register(SignInViewController.self) { resolver in + let vm = resolver.resolve(SignViewModel.self)! + return SignInViewController(signViewModel: vm) + } } } From b8b6d235ae1a27e6db569c458e78c7546ae5a2d2 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Wed, 27 Aug 2025 00:23:53 +0900 Subject: [PATCH 08/16] feat: bind LoginViewController to SignInViewController transition --- Projects/Data/Sources/Repository/SignRepository.swift | 7 ++----- .../LearnMate/Sources/Coordinator/AppCoordinator.swift | 5 +++++ Projects/Login/Sources/View/LoginViewController.swift | 4 ++-- Projects/Login/Sources/ViewModel/SignViewModel.swift | 2 ++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Projects/Data/Sources/Repository/SignRepository.swift b/Projects/Data/Sources/Repository/SignRepository.swift index 844de04..da804aa 100644 --- a/Projects/Data/Sources/Repository/SignRepository.swift +++ b/Projects/Data/Sources/Repository/SignRepository.swift @@ -10,11 +10,8 @@ import RxSwift import Alamofire public class DefaultSignRepository: SignRepository { - private let tokenRepository: TokenRepository - - public init(tokenRepository: TokenRepository) { - self.tokenRepository = tokenRepository - } + + public init() { } public func postEmail(email: String) -> Completable { return request(endpoint: "/api/auth/email") diff --git a/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift b/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift index d360751..65c1f63 100644 --- a/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift +++ b/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift @@ -36,6 +36,11 @@ final class DefaultAppCoordinator: AppCoordinator{ func start() { let loginViewController = dependency.injector.resolve(LoginViewController.self) + loginViewController.onPresentLmLogin = { [weak self] in + guard let self else { return } + let signInViewController = self.dependency.injector.resolve(SignInViewController.self) + self.navigationController.pushViewController(signInViewController, animated: true) + } self.navigationController.pushViewController(loginViewController, animated: true) // setNavigationBar() // setTabBarCoordinator() diff --git a/Projects/Login/Sources/View/LoginViewController.swift b/Projects/Login/Sources/View/LoginViewController.swift index 92eae3c..bf99654 100644 --- a/Projects/Login/Sources/View/LoginViewController.swift +++ b/Projects/Login/Sources/View/LoginViewController.swift @@ -15,6 +15,7 @@ import AuthenticationServices public class LoginViewController: BaseViewController, SFSafariViewControllerDelegate, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { let viewModel: LoginViewModel let loginView = LoginView() + public var onPresentLmLogin: (() -> Void)? public init(loginViewModel: LoginViewModel) { self.viewModel = loginViewModel @@ -61,8 +62,7 @@ public class LoginViewController: BaseViewController, SFSafariViewControllerDele } private func presentLmLogin() { - let signInViewController = SignInViewController() - self.navigationController?.pushViewController(signInViewController, animated: true) + onPresentLmLogin?() } private func presentGoogleLogin() { diff --git a/Projects/Login/Sources/ViewModel/SignViewModel.swift b/Projects/Login/Sources/ViewModel/SignViewModel.swift index 07d0fc4..ef0d376 100644 --- a/Projects/Login/Sources/ViewModel/SignViewModel.swift +++ b/Projects/Login/Sources/ViewModel/SignViewModel.swift @@ -16,6 +16,8 @@ protocol SignViewModelProtocol { public class SignViewModel: SignViewModelProtocol { private let disposeBag = DisposeBag() private let signUseCase: SignUseCase + public weak var signInViewCoordinator: SignInCoordinator? + public init(signUseCase: SignUseCase) { self.signUseCase = signUseCase } From a0a29fb03cfa4111920de889a8f5f642700ef407 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Wed, 27 Aug 2025 10:41:20 +0900 Subject: [PATCH 09/16] feat: set SignUpViewController DI --- .../Sources/Coordinator/Coordinator.swift | 1 + .../Login/Login.xcodeproj/project.pbxproj | 4 ++ .../Coordinator/SignUpCoordinator.swift | 54 +++++++++++++++++++ .../Sources/View/SignUpViewController.swift | 8 +-- .../Sources/ViewModel/SignViewModel.swift | 1 + 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 Projects/Login/Sources/Coordinator/SignUpCoordinator.swift diff --git a/Projects/CommonUI/Sources/Coordinator/Coordinator.swift b/Projects/CommonUI/Sources/Coordinator/Coordinator.swift index b2459f8..785ae88 100644 --- a/Projects/CommonUI/Sources/Coordinator/Coordinator.swift +++ b/Projects/CommonUI/Sources/Coordinator/Coordinator.swift @@ -16,6 +16,7 @@ public enum CoordinatorType { case stats case myPage case signIn + case signUp } public protocol Coordinator: AnyObject { diff --git a/Projects/Login/Login.xcodeproj/project.pbxproj b/Projects/Login/Login.xcodeproj/project.pbxproj index 3ccee4e..15f0774 100644 --- a/Projects/Login/Login.xcodeproj/project.pbxproj +++ b/Projects/Login/Login.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 950A0D642E5C5F7D00C07CF2 /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D632E5C5F7900C07CF2 /* SignUpViewController.swift */; }; 950A0D662E5CCB2F00C07CF2 /* SignViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D652E5CCB2900C07CF2 /* SignViewModel.swift */; }; 950A0D862E5DE6D000C07CF2 /* SignInCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D852E5DE6C600C07CF2 /* SignInCoordinator.swift */; }; + 950A0D8A2E5E0C8600C07CF2 /* SignUpCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D892E5E0C8000C07CF2 /* SignUpCoordinator.swift */; }; 9543B5BD011752760C9E8D48 /* Common.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A3E03A11BBC654DEC110ECE /* Common.framework */; }; A2DD99D036B33B4B67E19670 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 149968626029AF0A6483A522 /* RxSwift.framework */; }; A60EB3815F8DEA7C7B2A1E4D /* SnapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A78DFB39E6F2ADF7C38CFA79 /* SnapKit.framework */; }; @@ -70,6 +71,7 @@ 950A0D632E5C5F7900C07CF2 /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; 950A0D652E5CCB2900C07CF2 /* SignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignViewModel.swift; sourceTree = ""; }; 950A0D852E5DE6C600C07CF2 /* SignInCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInCoordinator.swift; sourceTree = ""; }; + 950A0D892E5E0C8000C07CF2 /* SignUpCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpCoordinator.swift; sourceTree = ""; }; 9A9C214589834256AC9DD6AB /* Then.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Then.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A6D197429DF4204F655968BA /* LoginTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoginTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A78DFB39E6F2ADF7C38CFA79 /* SnapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -168,6 +170,7 @@ isa = PBXGroup; children = ( 950A0D852E5DE6C600C07CF2 /* SignInCoordinator.swift */, + 950A0D892E5E0C8000C07CF2 /* SignUpCoordinator.swift */, ); path = Coordinator; sourceTree = ""; @@ -283,6 +286,7 @@ buildActionMask = 2147483647; files = ( 4027F4EA82D89F9520C87D34 /* LoginViewController.swift in Sources */, + 950A0D8A2E5E0C8600C07CF2 /* SignUpCoordinator.swift in Sources */, 92D7E2A0C6D4E3A6F57BAFB0 /* LoginViewModel.swift in Sources */, 950A0D662E5CCB2F00C07CF2 /* SignViewModel.swift in Sources */, 950A0D642E5C5F7D00C07CF2 /* SignUpViewController.swift in Sources */, diff --git a/Projects/Login/Sources/Coordinator/SignUpCoordinator.swift b/Projects/Login/Sources/Coordinator/SignUpCoordinator.swift new file mode 100644 index 0000000..b34f6ad --- /dev/null +++ b/Projects/Login/Sources/Coordinator/SignUpCoordinator.swift @@ -0,0 +1,54 @@ +// +// SignUpCoordinator.swift +// Login +// +// Created by 박지윤 on 8/27/25. +// + +import CommonUI +import UIKit + +public protocol SignUpCoordinator: Coordinator { + func showSignUpFlow() +} + +public class DefaultSignUpCoordinator: SignUpCoordinator { + public struct Dependency { + let signUpViewController: SignUpViewController + let navigationController: UINavigationController + weak var finishDelegate: CoordinatorFinishDelegate? + + public init(signUpViewController: SignUpViewController, + navigationController: UINavigationController, + finishDelegate: CoordinatorFinishDelegate? = nil) { + self.signUpViewController = signUpViewController + self.navigationController = navigationController + self.finishDelegate = finishDelegate + } + } + + let dependency: Dependency + public var childCoordinators: [Coordinator] = [] + public var navigationController: UINavigationController + public var type: CoordinatorType = .signUp + public var finishDelegate: CoordinatorFinishDelegate? + + public init(dependency: Dependency) { + self.dependency = dependency + self.navigationController = dependency.navigationController + self.finishDelegate = dependency.finishDelegate + dependency.signUpViewController.viewModel.signUpViewCoordinator = self + } + + public func start() { + setNavigationBar() + showSignUpFlow() + } + + func setNavigationBar() { + } + + public func showSignUpFlow() { + navigationController.pushViewController(dependency.signUpViewController, animated: true) + } +} diff --git a/Projects/Login/Sources/View/SignUpViewController.swift b/Projects/Login/Sources/View/SignUpViewController.swift index 91b88de..609c743 100644 --- a/Projects/Login/Sources/View/SignUpViewController.swift +++ b/Projects/Login/Sources/View/SignUpViewController.swift @@ -11,7 +11,9 @@ import SnapKit import RxSwift public class SignUpViewController: BaseViewController { - let signViewModel: SignViewModel + let viewModel: SignViewModel + public weak var signUpViewCoordinator: SignUpCoordinator? + let navigationBar = DefaultNavigationBar(leftImage: CommonUIAssets.IconBack ?? nil, rightImage: nil, title: "회원가입", @@ -26,7 +28,7 @@ public class SignUpViewController: BaseViewController { } public init(signViewModel: SignViewModel) { - self.signViewModel = signViewModel + self.viewModel = signViewModel super.init() } @@ -46,7 +48,7 @@ public class SignUpViewController: BaseViewController { private func bindActions() { signUpView.emailInputField.onEmailButtonTapped = { email in - self.signViewModel.postEmail(email: email) + self.viewModel.postEmail(email: email) } } diff --git a/Projects/Login/Sources/ViewModel/SignViewModel.swift b/Projects/Login/Sources/ViewModel/SignViewModel.swift index ef0d376..ce8b4ce 100644 --- a/Projects/Login/Sources/ViewModel/SignViewModel.swift +++ b/Projects/Login/Sources/ViewModel/SignViewModel.swift @@ -17,6 +17,7 @@ public class SignViewModel: SignViewModelProtocol { private let disposeBag = DisposeBag() private let signUseCase: SignUseCase public weak var signInViewCoordinator: SignInCoordinator? + public weak var signUpViewCoordinator: SignUpCoordinator? public init(signUseCase: SignUseCase) { self.signUseCase = signUseCase From 6420a29d1c20a63527e93fd288c7b5e2256f6676 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Wed, 27 Aug 2025 10:46:57 +0900 Subject: [PATCH 10/16] feat: add SignUpViewController dependency injection --- Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift | 5 +++++ Projects/LearnMate/Sources/DI/LoginAssembly.swift | 5 +++++ Projects/Login/Sources/View/SignInViewController.swift | 6 ++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift b/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift index 65c1f63..f92d587 100644 --- a/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift +++ b/Projects/LearnMate/Sources/Coordinator/AppCoordinator.swift @@ -39,6 +39,11 @@ final class DefaultAppCoordinator: AppCoordinator{ loginViewController.onPresentLmLogin = { [weak self] in guard let self else { return } let signInViewController = self.dependency.injector.resolve(SignInViewController.self) + signInViewController.onPresentSignUp = { [weak self] in + guard let self else { return } + let signUpViewController = self.dependency.injector.resolve(SignUpViewController.self) + self.navigationController.pushViewController(signUpViewController, animated: true) + } self.navigationController.pushViewController(signInViewController, animated: true) } self.navigationController.pushViewController(loginViewController, animated: true) diff --git a/Projects/LearnMate/Sources/DI/LoginAssembly.swift b/Projects/LearnMate/Sources/DI/LoginAssembly.swift index 793a018..f3981cc 100644 --- a/Projects/LearnMate/Sources/DI/LoginAssembly.swift +++ b/Projects/LearnMate/Sources/DI/LoginAssembly.swift @@ -35,5 +35,10 @@ public struct LoginAssembly: Assembly { let vm = resolver.resolve(SignViewModel.self)! return SignInViewController(signViewModel: vm) } + + container.register(SignUpViewController.self) { resolver in + let vm = resolver.resolve(SignViewModel.self)! + return SignUpViewController(signViewModel: vm) + } } } diff --git a/Projects/Login/Sources/View/SignInViewController.swift b/Projects/Login/Sources/View/SignInViewController.swift index c423cbb..e6c5871 100644 --- a/Projects/Login/Sources/View/SignInViewController.swift +++ b/Projects/Login/Sources/View/SignInViewController.swift @@ -19,6 +19,7 @@ public class SignInViewController: BaseViewController { isRightButtonHidden: true) let signInView = SignInView() + public var onPresentSignUp: (() -> Void)? public init(signViewModel: SignViewModel) { self.viewModel = signViewModel @@ -49,8 +50,9 @@ public class SignInViewController: BaseViewController { } private func presentSignUp() { - let signUpViewController = SignUpViewController(signViewModel: viewModel) - self.navigationController?.pushViewController(signUpViewController, animated: true) + onPresentSignUp?() +// let signUpViewController = SignUpViewController(signViewModel: viewModel) +// self.navigationController?.pushViewController(signUpViewController, animated: true) } private func bindTransition() { From b181aa3080b9edf3b55416eb18d5fa2542e05e08 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Thu, 28 Aug 2025 17:25:24 +0900 Subject: [PATCH 11/16] feat: add DefaultDTO & DefaultVO --- Projects/Data/Data.xcodeproj/project.pbxproj | 4 ++++ Projects/Data/Sources/DTO/DefaultDTO.swift | 21 +++++++++++++++++++ .../Domain/Domain.xcodeproj/project.pbxproj | 4 ++++ Projects/Domain/Sources/VO/DefaultVO.swift | 14 +++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 Projects/Data/Sources/DTO/DefaultDTO.swift create mode 100644 Projects/Domain/Sources/VO/DefaultVO.swift diff --git a/Projects/Data/Data.xcodeproj/project.pbxproj b/Projects/Data/Data.xcodeproj/project.pbxproj index 5a8cb7f..c9e0efb 100644 --- a/Projects/Data/Data.xcodeproj/project.pbxproj +++ b/Projects/Data/Data.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 684AAEA9796EED3F9FC592FC /* NetworkConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D46FD93B80D052843AD5063 /* NetworkConfiguration.swift */; }; 901ACA7B98089AB702ADA830 /* Domain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06FAA1459D11CCE724C34195 /* Domain.framework */; }; 950A0D702E5CCF0200C07CF2 /* SignRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D6F2E5CCEFD00C07CF2 /* SignRepository.swift */; }; + 950A0D902E6039D600C07CF2 /* DefaultDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D8F2E6039D300C07CF2 /* DefaultDTO.swift */; }; A334985695DC9388841BBC43 /* QuizRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCA951B89F2C3D20AA31F7F /* QuizRepository.swift */; }; B2F8FBFA915F696CCCA4152A /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3B0D3D8C7049B6856791C1D /* Alamofire.framework */; }; E1BFC73FB539432F6E12CD94 /* CourseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0730BC3657E24BCEA511A3C /* CourseRepository.swift */; }; @@ -43,6 +44,7 @@ 5D46FD93B80D052843AD5063 /* NetworkConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConfiguration.swift; sourceTree = ""; }; 77810122262C6CB16D4D47DA /* QuizDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizDTO.swift; sourceTree = ""; }; 950A0D6F2E5CCEFD00C07CF2 /* SignRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRepository.swift; sourceTree = ""; }; + 950A0D8F2E6039D300C07CF2 /* DefaultDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultDTO.swift; sourceTree = ""; }; A3B0D3D8C7049B6856791C1D /* Alamofire.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A44BC1E2E75FC256F832CA38 /* LoginDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginDTO.swift; sourceTree = ""; }; AAAC2885ACFE998578DC25E8 /* CourseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDTO.swift; sourceTree = ""; }; @@ -75,6 +77,7 @@ 647255CD65221C9CD4A43DED /* DTO */ = { isa = PBXGroup; children = ( + 950A0D8F2E6039D300C07CF2 /* DefaultDTO.swift */, AAAC2885ACFE998578DC25E8 /* CourseDTO.swift */, A44BC1E2E75FC256F832CA38 /* LoginDTO.swift */, 77810122262C6CB16D4D47DA /* QuizDTO.swift */, @@ -216,6 +219,7 @@ 950A0D702E5CCF0200C07CF2 /* SignRepository.swift in Sources */, E1BFC73FB539432F6E12CD94 /* CourseRepository.swift in Sources */, 34FD760EB97BB96E9D770BF0 /* LoginRepository.swift in Sources */, + 950A0D902E6039D600C07CF2 /* DefaultDTO.swift in Sources */, A334985695DC9388841BBC43 /* QuizRepository.swift in Sources */, E6785667C8E247C344474CBB /* TokenRepository.swift in Sources */, ); diff --git a/Projects/Data/Sources/DTO/DefaultDTO.swift b/Projects/Data/Sources/DTO/DefaultDTO.swift new file mode 100644 index 0000000..f339791 --- /dev/null +++ b/Projects/Data/Sources/DTO/DefaultDTO.swift @@ -0,0 +1,21 @@ +// +// DefaultDTO.swift +// Data +// +// Created by 박지윤 on 8/28/25. +// + +import Foundation +import Domain + +public struct DefaultDTO: Decodable { + public let is_success: Bool + public let code: String + public let message: String +} + +extension DefaultDTO { + func getMessage() -> DefaultVO { + return .init(message: message) + } +} diff --git a/Projects/Domain/Domain.xcodeproj/project.pbxproj b/Projects/Domain/Domain.xcodeproj/project.pbxproj index 8deaa4b..27d2c4a 100644 --- a/Projects/Domain/Domain.xcodeproj/project.pbxproj +++ b/Projects/Domain/Domain.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 950A0D692E5CCBB900C07CF2 /* SignUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */; }; 950A0D6B2E5CCBF000C07CF2 /* SignRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D6A2E5CCBE800C07CF2 /* SignRepository.swift */; }; 950A0D822E5DE10C00C07CF2 /* LoginUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D812E5DE10700C07CF2 /* LoginUseCase.swift */; }; + 950A0D922E603DA100C07CF2 /* DefaultVO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D912E603D9C00C07CF2 /* DefaultVO.swift */; }; 95369A7E2E28B8D9000C893F /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95369A7D2E28B8D1000C893F /* UIImage+Extension.swift */; }; A0CEC5A2E5A76EBD987CE37D /* StepVO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798CFC331192C9FE77F3446A /* StepVO.swift */; }; B0B0382EF5DFDACDD3EB8A75 /* QuizUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4565F6A02AFBF3D9A0D41B /* QuizUseCase.swift */; }; @@ -53,6 +54,7 @@ 950A0D682E5CCBB400C07CF2 /* SignUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUseCase.swift; sourceTree = ""; }; 950A0D6A2E5CCBE800C07CF2 /* SignRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRepository.swift; sourceTree = ""; }; 950A0D812E5DE10700C07CF2 /* LoginUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginUseCase.swift; sourceTree = ""; }; + 950A0D912E603D9C00C07CF2 /* DefaultVO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultVO.swift; sourceTree = ""; }; 95369A7D2E28B8D1000C893F /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 9A2E30672E510822AAF38EAD /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ABB8C62A6FE12783AD3819BE /* TokenUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenUseCase.swift; sourceTree = ""; }; @@ -153,6 +155,7 @@ 9C17CEB3595F4C3FA69C5BAF /* VO */ = { isa = PBXGroup; children = ( + 950A0D912E603D9C00C07CF2 /* DefaultVO.swift */, 747DBBCAB797E5E0A82177F2 /* CourseVO.swift */, FD08A7186FB9676854B7AEAC /* LoginVO.swift */, 24F9958F3DAC8013B440CEEA /* QuizVO.swift */, @@ -257,6 +260,7 @@ B0B0382EF5DFDACDD3EB8A75 /* QuizUseCase.swift in Sources */, 3E835D4F2E5F639557AC7C06 /* TokenUseCase.swift in Sources */, 5EB8590C9D2C002D0BDBC021 /* CourseVO.swift in Sources */, + 950A0D922E603DA100C07CF2 /* DefaultVO.swift in Sources */, 0DED9075FE34124C093D9FDF /* LoginVO.swift in Sources */, 95369A7E2E28B8D9000C893F /* UIImage+Extension.swift in Sources */, 950A0D692E5CCBB900C07CF2 /* SignUseCase.swift in Sources */, diff --git a/Projects/Domain/Sources/VO/DefaultVO.swift b/Projects/Domain/Sources/VO/DefaultVO.swift new file mode 100644 index 0000000..3ee9714 --- /dev/null +++ b/Projects/Domain/Sources/VO/DefaultVO.swift @@ -0,0 +1,14 @@ +// +// DefaultVO.swift +// Domain +// +// Created by 박지윤 on 8/28/25. +// + +public struct DefaultVO { + public let message: String + + public init(message: String) { + self.message = message + } +} From 9f7f5993332d515055e2e0d1592e14d251ec37fa Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Thu, 28 Aug 2025 17:26:54 +0900 Subject: [PATCH 12/16] feat: implement postEmail API & postConfirm API --- .../Sources/Component/LMInputField.swift | 33 +++++++++- .../Sources/View/Login/SignUpView.swift | 4 +- .../Sources/Repository/SignRepository.swift | 63 ++++++++++++------- .../RepositoryProtocol/SignRepository.swift | 4 +- .../Sources/UseCase/Login/SignUseCase.swift | 8 +-- .../Sources/View/SignUpViewController.swift | 48 +++++++++++++- .../Sources/ViewModel/SignViewModel.swift | 36 ++++++----- 7 files changed, 145 insertions(+), 51 deletions(-) diff --git a/Projects/CommonUI/Sources/Component/LMInputField.swift b/Projects/CommonUI/Sources/Component/LMInputField.swift index 12ea4d1..e298c54 100644 --- a/Projects/CommonUI/Sources/Component/LMInputField.swift +++ b/Projects/CommonUI/Sources/Component/LMInputField.swift @@ -64,7 +64,7 @@ public class LMInputField: UIStackView { warningLabel = warningLabel.then { $0.text = waringText - $0.textColor = CommonUIAssets.LMRed2 + $0.textColor = .clear $0.font = UIFont.systemFont(ofSize: 13, weight: .light) } } @@ -90,7 +90,7 @@ public class LMInputField: UIStackView { let emailButton = LMButton(textColor: CommonUIAssets.LMBlack, bgColor: CommonUIAssets.LMOrange1).then { $0.setTitle(buttonTitle, for: .normal) - $0.addTarget(self, action: #selector(authButtonTapped), for: .touchUpInside) + $0.addTarget(self, action: #selector(emailButtonTapped), for: .touchUpInside) } [inputTextField, emailButton] @@ -147,7 +147,34 @@ public class LMInputField: UIStackView { } } - @objc private func authButtonTapped() { + @objc private func emailButtonTapped() { onEmailButtonTapped?(inputTextField.text ?? "") } + + public func showWarning() { + warningLabel.textColor = CommonUIAssets.LMRed2 + } + + public func hideWarning() { + warningLabel.textColor = .clear + } + + public func disableButton(buttonTitle: String) { + for subview in self.arrangedSubviews { + if let stackView = subview as? UIStackView { + for stackSubview in stackView.arrangedSubviews { + if let textField = stackSubview as? LMTextField { + textField.isEnabled = false + } + + if let button = stackSubview as? LMButton { + button.setTitle(buttonTitle, for: .normal) + button.isEnabled = false + button.backgroundColor = CommonUIAssets.LMGray5 + button.setTitleColor(CommonUIAssets.LMWhite, for: .normal) + } + } + } + } + } } diff --git a/Projects/CommonUI/Sources/View/Login/SignUpView.swift b/Projects/CommonUI/Sources/View/Login/SignUpView.swift index 93a0418..9d57275 100644 --- a/Projects/CommonUI/Sources/View/Login/SignUpView.swift +++ b/Projects/CommonUI/Sources/View/Login/SignUpView.swift @@ -22,7 +22,7 @@ open class SignUpView: UIView { inputPlaceholder: " 이메일을 입력하세요", warningText: " 이메일 형식이 올바르지 않습니다", buttonTitle: "인증 요청") - let authenticationInputField = LMInputField(inputType: .email, + public let confirmInputField = LMInputField(inputType: .email, inputText: "인증번호", inputPlaceholder: " 인증번호를 입력하세요", warningText: " 인증번호가 일치하지 않습니다", @@ -61,7 +61,7 @@ open class SignUpView: UIView { func initUI() { self.addSubview(inputFieldStackView) - [nameInputField, emailInputField, authenticationInputField, passwordInputField, passwordCheckInputField] + [nameInputField, emailInputField, confirmInputField, passwordInputField, passwordCheckInputField] .forEach { inputFieldStackView.addArrangedSubview($0) } inputFieldStackView.snp.makeConstraints { diff --git a/Projects/Data/Sources/Repository/SignRepository.swift b/Projects/Data/Sources/Repository/SignRepository.swift index da804aa..987a9ae 100644 --- a/Projects/Data/Sources/Repository/SignRepository.swift +++ b/Projects/Data/Sources/Repository/SignRepository.swift @@ -13,33 +13,52 @@ public class DefaultSignRepository: SignRepository { public init() { } - public func postEmail(email: String) -> Completable { - return request(endpoint: "/api/auth/email") + public func postEmail(email: String) -> Single { + let params = ["email": email] + return request(endpoint: "/api/auth/email", + parameters: params, + responseType: DefaultDTO.self) + .map { dto in + return dto.getMessage() + } } - - public func postConfirm(email: String, code: String) -> Completable { - return request(endpoint: "/api/auth/email/confirm") + + public func postConfirm(email: String, code: String) -> Single { + let params = ["email": email, "code": code] + return request(endpoint: "/api/auth/email/confirm", + parameters: params, + responseType: DefaultDTO.self) + .map { dto in + return dto.getMessage() + } } - private func request(endpoint: String) -> Completable { - return Completable.create { completable in + private func request( + endpoint: String, + parameters: [String: Any]? = nil, + responseType: T.Type + ) -> Single { + return Single.create { single in let url = "\(NetworkConfiguration.baseUrl)\(endpoint)" - var headers: HTTPHeaders = [:] - let request = AF.request(url, - method: .get, - encoding: URLEncoding.queryString, - headers: headers) - .validate() - .response { response in - if let error = response.error { - print("❌ API 응답 실패") - completable(.error(error)) - } else { - print("✅ API 응답 성공") - completable(.completed) - } + let headers: HTTPHeaders = [:] + let request = AF.request( + url, + method: .post, + parameters: parameters, + encoding: JSONEncoding.default, + headers: headers + ) + .validate() + .responseDecodable(of: responseType) { response in + switch response.result { + case .success(let value): + print("✅ API 응답 성공: \(value)") + single(.success(value)) + case .failure(let error): + print("❌ API 응답 실패: \(error)") + single(.failure(error)) } - + } return Disposables.create { request.cancel() } } } diff --git a/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift index 44fde6a..3fe85f4 100644 --- a/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift +++ b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift @@ -8,6 +8,6 @@ import RxSwift public protocol SignRepository { - func postEmail(email: String) -> Completable - func postConfirm(email: String, code: String) -> Completable + func postEmail(email: String) -> Single + func postConfirm(email: String, code: String) -> Single } diff --git a/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift index a590fdd..8874f35 100644 --- a/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift +++ b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift @@ -8,8 +8,8 @@ import RxSwift public protocol SignUseCase { - func postEmail(email: String) -> Completable - func postConfirm(email: String, code: String) -> Completable + func postEmail(email: String) -> Single + func postConfirm(email: String, code: String) -> Single } public final class DefaultSignUseCase: SignUseCase { @@ -19,11 +19,11 @@ public final class DefaultSignUseCase: SignUseCase { self.repository = repository } - public func postEmail(email: String) -> Completable { + public func postEmail(email: String) -> Single { return repository.postEmail(email: email) } - public func postConfirm(email: String, code: String) -> Completable { + public func postConfirm(email: String, code: String) -> Single { return repository.postConfirm(email: email, code: code) } } diff --git a/Projects/Login/Sources/View/SignUpViewController.swift b/Projects/Login/Sources/View/SignUpViewController.swift index 609c743..dcf1010 100644 --- a/Projects/Login/Sources/View/SignUpViewController.swift +++ b/Projects/Login/Sources/View/SignUpViewController.swift @@ -26,6 +26,7 @@ public class SignUpViewController: BaseViewController { bgColor: CommonUIAssets.LMOrange1).then { $0.setTitle("회원가입", for: .normal) } + private var email: String = "" public init(signViewModel: SignViewModel) { self.viewModel = signViewModel @@ -44,17 +45,60 @@ public class SignUpViewController: BaseViewController { super.viewDidLoad() bindActions() bindTransition() + +// signUpView.authenticationInputField.disableButton(buttonTitle: ) + + viewModel.onEmailSuccess = { [weak self] in + DispatchQueue.main.async { + self?.signUpView.emailInputField.disableButton(buttonTitle: "전송 완료") + } + } + + viewModel.onConfirmSuccess = { [weak self] in + DispatchQueue.main.async { + self?.signUpView.confirmInputField.hideWarning() + self?.signUpView.confirmInputField.disableButton(buttonTitle: "인증 완료") + } + } + + viewModel.onConfirmFailure = { [weak self] in + DispatchQueue.main.async { + self?.signUpView.confirmInputField.showWarning() + } + } } private func bindActions() { - signUpView.emailInputField.onEmailButtonTapped = { email in - self.viewModel.postEmail(email: email) + signUpView.emailInputField.onEmailButtonTapped = { [weak self] email in + guard let self = self else { return } + + if self.isValidEmail(email) { + print("유효한 이메일: \(email)") + self.email = email + self.viewModel.postEmail(email: email) + self.signUpView.emailInputField.hideWarning() + } else { + print("유효하지 않은 이메일: \(email)") + self.signUpView.emailInputField.showWarning() + } + } + + signUpView.confirmInputField.onEmailButtonTapped = { [weak self] code in + guard let self = self else { return } + + self.viewModel.postConfirm(email: email, code: code) } } private func bindTransition() { } + private func isValidEmail(_ email: String) -> Bool { + let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex) + return emailPredicate.evaluate(with: email) + } + public override func setupViewProperty() { view.backgroundColor = CommonUIAssets.LMWhite } diff --git a/Projects/Login/Sources/ViewModel/SignViewModel.swift b/Projects/Login/Sources/ViewModel/SignViewModel.swift index ce8b4ce..95b642d 100644 --- a/Projects/Login/Sources/ViewModel/SignViewModel.swift +++ b/Projects/Login/Sources/ViewModel/SignViewModel.swift @@ -18,6 +18,9 @@ public class SignViewModel: SignViewModelProtocol { private let signUseCase: SignUseCase public weak var signInViewCoordinator: SignInCoordinator? public weak var signUpViewCoordinator: SignUpCoordinator? + public var onEmailSuccess: (() -> Void)? + public var onConfirmSuccess: (() -> Void)? + public var onConfirmFailure: (() -> Void)? public init(signUseCase: SignUseCase) { self.signUseCase = signUseCase @@ -25,25 +28,26 @@ public class SignViewModel: SignViewModelProtocol { func postEmail(email: String) { signUseCase.postEmail(email: email) - .subscribe( - onCompleted: { - print("이메일 전송 성공") - }, - onError: { error in - print("이메일 전송 실패: \(error)") - } - ).disposed(by: disposeBag) + .subscribe(onSuccess: { [weak self] response in + print("이메일 전송 성공: \(response.message)") + self?.onEmailSuccess?() + }, onFailure: { error in + print("이메일 전송 실패: \(error)") + }) + .disposed(by: disposeBag) } func postConfirm(email: String, code: String) { signUseCase.postConfirm(email: email, code: code) - .subscribe( - onCompleted: { - print("이메일 전송 성공") - }, - onError: { error in - print("이메일 전송 실패: \(error)") - } - ).disposed(by: disposeBag) + .subscribe(onSuccess: { [weak self] response in + guard let self = self else { return } + print("이메일 인증 성공: \(response.message)") + self.onConfirmSuccess?() + }, onFailure: { [weak self] error in + guard let self = self else { return } + print("이메일 인증 실패: \(error)") + self.onConfirmFailure?() + }) + .disposed(by: disposeBag) } } From 8c9a3e5fc4120cc262c71a6ba0bf713f2b86deea Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Thu, 28 Aug 2025 17:57:26 +0900 Subject: [PATCH 13/16] feat: setupKeyboardDismissGesture --- .../Login/Sources/View/SignUpViewController.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Projects/Login/Sources/View/SignUpViewController.swift b/Projects/Login/Sources/View/SignUpViewController.swift index dcf1010..fad4613 100644 --- a/Projects/Login/Sources/View/SignUpViewController.swift +++ b/Projects/Login/Sources/View/SignUpViewController.swift @@ -45,6 +45,7 @@ public class SignUpViewController: BaseViewController { super.viewDidLoad() bindActions() bindTransition() + setupKeyboardDismissGesture() // signUpView.authenticationInputField.disableButton(buttonTitle: ) @@ -113,6 +114,16 @@ public class SignUpViewController: BaseViewController { public override func setupDelegate() { } + + private func setupKeyboardDismissGesture() { + let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + tap.cancelsTouchesInView = false + view.addGestureRecognizer(tap) + } + + @objc private func dismissKeyboard() { + view.endEditing(true) + } public override func setupLayout() { navigationBar.snp.makeConstraints { From 1256d337d9b06d99984f5d72984c36592ee98fab Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Thu, 28 Aug 2025 18:18:49 +0900 Subject: [PATCH 14/16] feat: input validation for signUp form --- .../Sources/Component/LMInputField.swift | 8 +++-- .../Sources/View/Login/SignUpView.swift | 6 ++-- .../Sources/View/SignUpViewController.swift | 32 +++++++++++++++++-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Projects/CommonUI/Sources/Component/LMInputField.swift b/Projects/CommonUI/Sources/Component/LMInputField.swift index e298c54..3c317d1 100644 --- a/Projects/CommonUI/Sources/Component/LMInputField.swift +++ b/Projects/CommonUI/Sources/Component/LMInputField.swift @@ -155,10 +155,14 @@ public class LMInputField: UIStackView { warningLabel.textColor = CommonUIAssets.LMRed2 } - public func hideWarning() { + public func hideWarning() { warningLabel.textColor = .clear } - + + public func currentText() -> String { + return inputTextField.text ?? "" + } + public func disableButton(buttonTitle: String) { for subview in self.arrangedSubviews { if let stackView = subview as? UIStackView { diff --git a/Projects/CommonUI/Sources/View/Login/SignUpView.swift b/Projects/CommonUI/Sources/View/Login/SignUpView.swift index 9d57275..fc76e7f 100644 --- a/Projects/CommonUI/Sources/View/Login/SignUpView.swift +++ b/Projects/CommonUI/Sources/View/Login/SignUpView.swift @@ -14,7 +14,7 @@ import Then open class SignUpView: UIView { var inputFieldStackView = UIStackView() - let nameInputField = LMInputField(inputText: "이름", + public let nameInputField = LMInputField(inputText: "이름", inputPlaceholder: "이름을 입력하세요", warningText: " 이름은 필수입니다") public let emailInputField = LMInputField(inputType: .email, @@ -27,11 +27,11 @@ open class SignUpView: UIView { inputPlaceholder: " 인증번호를 입력하세요", warningText: " 인증번호가 일치하지 않습니다", buttonTitle: "확인") - let passwordInputField = LMInputField(inputType: .password, + public let passwordInputField = LMInputField(inputType: .password, inputText: "비밀번호", inputPlaceholder: "비밀번호를 입력하세요", warningText: " 비밀번호 형식이 올바르지 않습니다") - let passwordCheckInputField = LMInputField(inputText: "비밀번호 확인", + public let passwordCheckInputField = LMInputField(inputText: "비밀번호 확인", inputPlaceholder: "비밀번호를 한번 더 입력하세요", warningText: " 비밀번호가 일치하지 않습니다") diff --git a/Projects/Login/Sources/View/SignUpViewController.swift b/Projects/Login/Sources/View/SignUpViewController.swift index fad4613..c654d5b 100644 --- a/Projects/Login/Sources/View/SignUpViewController.swift +++ b/Projects/Login/Sources/View/SignUpViewController.swift @@ -47,8 +47,6 @@ public class SignUpViewController: BaseViewController { bindTransition() setupKeyboardDismissGesture() -// signUpView.authenticationInputField.disableButton(buttonTitle: ) - viewModel.onEmailSuccess = { [weak self] in DispatchQueue.main.async { self?.signUpView.emailInputField.disableButton(buttonTitle: "전송 완료") @@ -123,6 +121,36 @@ public class SignUpViewController: BaseViewController { @objc private func dismissKeyboard() { view.endEditing(true) + validateOnKeyboardDismiss() + } + + private func validateOnKeyboardDismiss() { + let name = signUpView.nameInputField.currentText() + if name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + signUpView.nameInputField.showWarning() + } else { + signUpView.nameInputField.hideWarning() + } + + let password = signUpView.passwordInputField.currentText() + if isValidPassword(password) { + signUpView.passwordInputField.hideWarning() + } else { + signUpView.passwordInputField.showWarning() + } + + let confirm = signUpView.passwordCheckInputField.currentText() + if !password.isEmpty && password == confirm { + signUpView.passwordCheckInputField.hideWarning() + } else { + signUpView.passwordCheckInputField.showWarning() + } + } + + private func isValidPassword(_ password: String) -> Bool { + let passwordRegex = "^(?=.*[a-z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>])[A-Za-z\\d!@#$%^&*(),.?\":{}|<>]{8,}$" + let predicate = NSPredicate(format: "SELF MATCHES %@", passwordRegex) + return predicate.evaluate(with: password) } public override func setupLayout() { From 9077bd831452dd323e829a0f2d3769535d8bb3f7 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Thu, 28 Aug 2025 18:33:46 +0900 Subject: [PATCH 15/16] feat: implement postSignUp API --- .../Data/Sources/Repository/SignRepository.swift | 10 ++++++++++ .../Sources/RepositoryProtocol/SignRepository.swift | 1 + .../Domain/Sources/UseCase/Login/SignUseCase.swift | 5 +++++ .../Login/Sources/ViewModel/SignViewModel.swift | 13 +++++++++++++ 4 files changed, 29 insertions(+) diff --git a/Projects/Data/Sources/Repository/SignRepository.swift b/Projects/Data/Sources/Repository/SignRepository.swift index 987a9ae..123bb61 100644 --- a/Projects/Data/Sources/Repository/SignRepository.swift +++ b/Projects/Data/Sources/Repository/SignRepository.swift @@ -33,6 +33,16 @@ public class DefaultSignRepository: SignRepository { } } + public func postSignUp(username: String, email: String, password: String) -> Single { + let params = ["username": username, "email": email, "password": password] + return request(endpoint: "/api/auth/sign-up", + parameters: params, + responseType: DefaultDTO.self) + .map { dto in + return dto.getMessage() + } + } + private func request( endpoint: String, parameters: [String: Any]? = nil, diff --git a/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift index 3fe85f4..8f231b5 100644 --- a/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift +++ b/Projects/Domain/Sources/RepositoryProtocol/SignRepository.swift @@ -10,4 +10,5 @@ import RxSwift public protocol SignRepository { 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/UseCase/Login/SignUseCase.swift b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift index 8874f35..d47e6a2 100644 --- a/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift +++ b/Projects/Domain/Sources/UseCase/Login/SignUseCase.swift @@ -10,6 +10,7 @@ import RxSwift public protocol SignUseCase { func postEmail(email: String) -> Single func postConfirm(email: String, code: String) -> Single + func postSignUp(username: String, email: String, password: String) -> Single } public final class DefaultSignUseCase: SignUseCase { @@ -26,4 +27,8 @@ public final class DefaultSignUseCase: SignUseCase { public func postConfirm(email: String, code: String) -> Single { return repository.postConfirm(email: email, code: code) } + + public func postSignUp(username: String, email: String, password: String) -> Single { + return repository.postSignUp(username: username, email: email, password: password) + } } diff --git a/Projects/Login/Sources/ViewModel/SignViewModel.swift b/Projects/Login/Sources/ViewModel/SignViewModel.swift index 95b642d..612aa0d 100644 --- a/Projects/Login/Sources/ViewModel/SignViewModel.swift +++ b/Projects/Login/Sources/ViewModel/SignViewModel.swift @@ -11,6 +11,7 @@ import RxSwift protocol SignViewModelProtocol { func postEmail(email: String) func postConfirm(email: String, code: String) + func postSignUp(username: String, email: String, password: String) } public class SignViewModel: SignViewModelProtocol { @@ -50,4 +51,16 @@ public class SignViewModel: SignViewModelProtocol { }) .disposed(by: disposeBag) } + + func postSignUp(username: String, email: String, password: String) { + signUseCase.postSignUp(username: username, email: email, password: password) + .subscribe(onSuccess: { [weak self] response in + guard let self = self else { return } + print("회원가입 성공: \(response.message)") + }, onFailure: { [weak self] error in + guard let self = self else { return } + print("회원가입 실패: \(error)") + }) + .disposed(by: disposeBag) + } } From 2e9dd85ed1a4ca558733b3d42ad39fa5e31a6a13 Mon Sep 17 00:00:00 2001 From: Bibi-urssu Date: Sun, 7 Sep 2025 16:53:03 +0900 Subject: [PATCH 16/16] feat: implement postSignUp API - 2 --- .../CommonUI.xcodeproj/project.pbxproj | 12 ++++++++ .../Sources/Component/LMInputField.swift | 6 ++-- .../Extension/UIStackView+Extension.swift | 17 +++++++++++ .../Sources/View/SignUpViewController.swift | 29 +++++++++++++++++++ .../Sources/ViewModel/SignViewModel.swift | 3 ++ 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 Projects/CommonUI/Sources/Extension/UIStackView+Extension.swift diff --git a/Projects/CommonUI/CommonUI.xcodeproj/project.pbxproj b/Projects/CommonUI/CommonUI.xcodeproj/project.pbxproj index 4b22f8d..1ae3142 100644 --- a/Projects/CommonUI/CommonUI.xcodeproj/project.pbxproj +++ b/Projects/CommonUI/CommonUI.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 950A0D562E5C29D000C07CF2 /* LMTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D552E5C29CC00C07CF2 /* LMTextField.swift */; }; 950A0D602E5C3C7000C07CF2 /* LMButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D5F2E5C3C6D00C07CF2 /* LMButton.swift */; }; 950A0D622E5C562700C07CF2 /* LMInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D612E5C561400C07CF2 /* LMInputField.swift */; }; + 950A0D962E605CEA00C07CF2 /* UIStackView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950A0D952E605CE300C07CF2 /* UIStackView+Extension.swift */; }; BAD8B768F782046D4AA1C073 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3DE048BEDCB92B48F401061 /* RxCocoa.framework */; }; BE81B1F3E60D37D75A058D2B /* SnapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CCDC15081A22BAED6318E3E /* SnapKit.framework */; }; C2B0F8237715D14D8797DBC9 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012F45F908769FA7C3C0792F /* LoginView.swift */; }; @@ -74,6 +75,7 @@ 950A0D552E5C29CC00C07CF2 /* LMTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMTextField.swift; sourceTree = ""; }; 950A0D5F2E5C3C6D00C07CF2 /* LMButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMButton.swift; sourceTree = ""; }; 950A0D612E5C561400C07CF2 /* LMInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMInputField.swift; sourceTree = ""; }; + 950A0D952E605CE300C07CF2 /* UIStackView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+Extension.swift"; sourceTree = ""; }; 9CCDC15081A22BAED6318E3E /* SnapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BACC7259FC0C14CB352A4E6B /* OptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionView.swift; sourceTree = ""; }; C28FE6392E1612667826E5C5 /* DiaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryView.swift; sourceTree = ""; }; @@ -184,6 +186,14 @@ path = Component; sourceTree = ""; }; + 950A0D942E605CE100C07CF2 /* Extension */ = { + isa = PBXGroup; + children = ( + 950A0D952E605CE300C07CF2 /* UIStackView+Extension.swift */, + ); + path = Extension; + sourceTree = ""; + }; 9786E1056828B1E6D5EDAB7C /* View */ = { isa = PBXGroup; children = ( @@ -208,6 +218,7 @@ BADD047B94176A526A5B7FB2 /* Sources */ = { isa = PBXGroup; children = ( + 950A0D942E605CE100C07CF2 /* Extension */, 950A0D522E5C296400C07CF2 /* Component */, 15CD642D82E9115C7976C408 /* Assets */, 88FA467F361B11350139B775 /* Base */, @@ -336,6 +347,7 @@ 7DC59B80630854028C7C80F4 /* TuistBundle+CommonUI.swift in Sources */, F3DA12FF4D18AE405F2F6B08 /* BaseViewController.swift in Sources */, E0865F2849CCB89CC83D3B77 /* Coordinator.swift in Sources */, + 950A0D962E605CEA00C07CF2 /* UIStackView+Extension.swift in Sources */, E055AA66777B1D4CC8C884E4 /* CommonUIAssets.swift in Sources */, 950A0D4F2E5AADB500C07CF2 /* SignUpView.swift in Sources */, F7673E4248628D67F3542848 /* ChatView.swift in Sources */, diff --git a/Projects/CommonUI/Sources/Component/LMInputField.swift b/Projects/CommonUI/Sources/Component/LMInputField.swift index 3c317d1..fa26590 100644 --- a/Projects/CommonUI/Sources/Component/LMInputField.swift +++ b/Projects/CommonUI/Sources/Component/LMInputField.swift @@ -155,14 +155,14 @@ public class LMInputField: UIStackView { warningLabel.textColor = CommonUIAssets.LMRed2 } - public func hideWarning() { + public func hideWarning() { warningLabel.textColor = .clear } - + public func currentText() -> String { return inputTextField.text ?? "" } - + public func disableButton(buttonTitle: String) { for subview in self.arrangedSubviews { if let stackView = subview as? UIStackView { diff --git a/Projects/CommonUI/Sources/Extension/UIStackView+Extension.swift b/Projects/CommonUI/Sources/Extension/UIStackView+Extension.swift new file mode 100644 index 0000000..99ebac6 --- /dev/null +++ b/Projects/CommonUI/Sources/Extension/UIStackView+Extension.swift @@ -0,0 +1,17 @@ +// +// UIStackView+Extension.swift +// CommonUI +// +// Created by 박지윤 on 8/28/25. +// + +import UIKit + +extension UIStackView { + public func getText() -> String { + return arrangedSubviews + .compactMap { $0 as? LMTextField } + .map { $0.text ?? "" } + .joined(separator: " ") + } +} diff --git a/Projects/Login/Sources/View/SignUpViewController.swift b/Projects/Login/Sources/View/SignUpViewController.swift index c654d5b..7d1abfe 100644 --- a/Projects/Login/Sources/View/SignUpViewController.swift +++ b/Projects/Login/Sources/View/SignUpViewController.swift @@ -87,6 +87,35 @@ public class SignUpViewController: BaseViewController { self.viewModel.postConfirm(email: email, code: code) } + + signUpButton.rx.tap + .subscribe(onNext: { [weak self] in + guard let self = self else { return } + + let name = self.signUpView.nameInputField.currentText() + let email = self.signUpView.emailInputField.currentText() + let password = self.signUpView.passwordInputField.currentText() + let confirm = self.signUpView.passwordCheckInputField.currentText() + + print("회원가입 버튼 탭 - name: \(name), email: \(email), password: \(password), confirm: \(confirm)") + + // 조건 체크 + let isNameValid = !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + let isEmailVerified = !self.email.isEmpty // 이메일 인증 완료 여부 + let isPasswordValid = self.isValidPassword(password) + let isConfirmValid = !password.isEmpty && password == confirm + + if isNameValid && isEmailVerified && isPasswordValid && isConfirmValid { + self.postSignUp(username: name, email: email, password: password) + } else { + print("회원가입 조건 미충족") + } + }) + .disposed(by: disposeBag) + } + + private func postSignUp(username: String, email: String, password: String) { + self.viewModel.postSignUp(username: username, email: email, password: password) } private func bindTransition() { diff --git a/Projects/Login/Sources/ViewModel/SignViewModel.swift b/Projects/Login/Sources/ViewModel/SignViewModel.swift index 612aa0d..4c8cf55 100644 --- a/Projects/Login/Sources/ViewModel/SignViewModel.swift +++ b/Projects/Login/Sources/ViewModel/SignViewModel.swift @@ -7,6 +7,7 @@ import Domain import RxSwift +import RxRelay protocol SignViewModelProtocol { func postEmail(email: String) @@ -22,6 +23,7 @@ public class SignViewModel: SignViewModelProtocol { public var onEmailSuccess: (() -> Void)? public var onConfirmSuccess: (() -> Void)? public var onConfirmFailure: (() -> Void)? + public let emailVerified = BehaviorRelay(value: false) public init(signUseCase: SignUseCase) { self.signUseCase = signUseCase @@ -44,6 +46,7 @@ public class SignViewModel: SignViewModelProtocol { guard let self = self else { return } print("이메일 인증 성공: \(response.message)") self.onConfirmSuccess?() + self.emailVerified.accept(true) }, onFailure: { [weak self] error in guard let self = self else { return } print("이메일 인증 실패: \(error)")