Skip to content

Commit

Permalink
Merge pull request #96 from team-aliens/82-email-verify-in-signup-fea…
Browse files Browse the repository at this point in the history
…ture

merge :: 회원가입 - 인증 이메일 입력 Feature
  • Loading branch information
baekteun authored Oct 27, 2022
2 parents 59f06e9 + 5cb82df commit 0cd556a
Show file tree
Hide file tree
Showing 21 changed files with 440 additions and 10 deletions.
6 changes: 6 additions & 0 deletions Projects/App/Sources/Application/DI/AppComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public extension AppComponent {
var signinComponent: SigninComponent {
SigninComponent(parent: self)
}
var signupEmailVerifyComponent: SignupEmailVerifyComponent {
SignupEmailVerifyComponent(parent: self)
}
var signupEmailAuthCodeVerifyComponent: SignupEmailAuthCodeVerifyComponent {
SignupEmailAuthCodeVerifyComponent(parent: self)
}
}

// MARK: - Main
Expand Down
48 changes: 48 additions & 0 deletions Projects/App/Sources/Application/NeedleGenerated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,38 @@ private class SchoolCodeDependencyc0114744c1c8c7843672Provider: SchoolCodeDepend
private func factoryb65c1efbf06b87162473f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject {
return SchoolCodeDependencyc0114744c1c8c7843672Provider(appComponent: parent1(component) as! AppComponent)
}
private class SignupEmailAuthCodeVerifyDependencyaf9da1ebf0e9e5f1b708Provider: SignupEmailAuthCodeVerifyDependency {
var sendAuthCodeUseCase: any SendAuthCodeUseCase {
return appComponent.sendAuthCodeUseCase
}
var verifyAuthCodeUseCase: any VerifyAuthCodeUseCase {
return appComponent.verifyAuthCodeUseCase
}
private let appComponent: AppComponent
init(appComponent: AppComponent) {
self.appComponent = appComponent
}
}
/// ^->AppComponent->SignupEmailAuthCodeVerifyComponent
private func factoryb06be35aa893adde971bf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject {
return SignupEmailAuthCodeVerifyDependencyaf9da1ebf0e9e5f1b708Provider(appComponent: parent1(component) as! AppComponent)
}
private class SignupEmailVerifyDependencyf9d372ac752ee19b78caProvider: SignupEmailVerifyDependency {
var checkDuplicateEmailUseCase: any CheckDuplicateEmailUseCase {
return appComponent.checkDuplicateEmailUseCase
}
var signupEmailAuthCodeVerifyComponent: SignupEmailAuthCodeVerifyComponent {
return appComponent.signupEmailAuthCodeVerifyComponent
}
private let appComponent: AppComponent
init(appComponent: AppComponent) {
self.appComponent = appComponent
}
}
/// ^->AppComponent->SignupEmailVerifyComponent
private func factory3b1904c76335d70151ebf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject {
return SignupEmailVerifyDependencyf9d372ac752ee19b78caProvider(appComponent: parent1(component) as! AppComponent)
}
private class MainTabDependency2826cdb310ed0b17a725Provider: MainTabDependency {
var homeComponent: HomeComponent {
return appComponent.homeComponent
Expand Down Expand Up @@ -110,6 +142,8 @@ extension AppComponent: Registration {
localTable["schoolCodeComponent-SchoolCodeComponent"] = { self.schoolCodeComponent as Any }
localTable["findIDComponent-FindIDComponent"] = { self.findIDComponent as Any }
localTable["signinComponent-SigninComponent"] = { self.signinComponent as Any }
localTable["signupEmailVerifyComponent-SignupEmailVerifyComponent"] = { self.signupEmailVerifyComponent as Any }
localTable["signupEmailAuthCodeVerifyComponent-SignupEmailAuthCodeVerifyComponent"] = { self.signupEmailAuthCodeVerifyComponent as Any }
localTable["mainTabComponent-MainTabComponent"] = { self.mainTabComponent as Any }
localTable["homeComponent-HomeComponent"] = { self.homeComponent as Any }
localTable["remoteStudentsDataSource-any RemoteStudentsDataSource"] = { self.remoteStudentsDataSource as Any }
Expand All @@ -136,6 +170,18 @@ extension SchoolCodeComponent: Registration {
keyPathToName[\SchoolCodeDependency.checkSchoolCodeUseCase] = "checkSchoolCodeUseCase-any CheckSchoolCodeUseCase"
}
}
extension SignupEmailAuthCodeVerifyComponent: Registration {
public func registerItems() {
keyPathToName[\SignupEmailAuthCodeVerifyDependency.sendAuthCodeUseCase] = "sendAuthCodeUseCase-any SendAuthCodeUseCase"
keyPathToName[\SignupEmailAuthCodeVerifyDependency.verifyAuthCodeUseCase] = "verifyAuthCodeUseCase-any VerifyAuthCodeUseCase"
}
}
extension SignupEmailVerifyComponent: Registration {
public func registerItems() {
keyPathToName[\SignupEmailVerifyDependency.checkDuplicateEmailUseCase] = "checkDuplicateEmailUseCase-any CheckDuplicateEmailUseCase"
keyPathToName[\SignupEmailVerifyDependency.signupEmailAuthCodeVerifyComponent] = "signupEmailAuthCodeVerifyComponent-SignupEmailAuthCodeVerifyComponent"
}
}
extension MainTabComponent: Registration {
public func registerItems() {
keyPathToName[\MainTabDependency.homeComponent] = "homeComponent-HomeComponent"
Expand Down Expand Up @@ -175,6 +221,8 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi
private func register1() {
registerProviderFactory("^->AppComponent", factoryEmptyDependencyProvider)
registerProviderFactory("^->AppComponent->SchoolCodeComponent", factoryb65c1efbf06b87162473f47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->SignupEmailAuthCodeVerifyComponent", factoryb06be35aa893adde971bf47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->SignupEmailVerifyComponent", factory3b1904c76335d70151ebf47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->MainTabComponent", factory1ab5a747ddf21e1393f9f47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->SigninComponent", factory2882a056d84a613debccf47b58f8f304c97af4d5)
registerProviderFactory("^->AppComponent->HomeComponent", factory67229cdf0f755562b2b1f47b58f8f304c97af4d5)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import SwiftUI

public struct RootPresentationModeKey: EnvironmentKey {
public static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode())
}

public extension EnvironmentValues {
var rootPresentationMode: Binding<RootPresentationMode> {
get { return self[RootPresentationModeKey.self] }
set { self[RootPresentationModeKey.self] = newValue }
}
}

public typealias RootPresentationMode = Bool

public extension RootPresentationMode {
mutating func dismiss() {
self.toggle()
}
}
23 changes: 23 additions & 0 deletions Projects/Features/BaseFeature/Sources/HideKeyboard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import UIKit

public extension UIApplication {
func hideKeyboard() {
guard
let scene = connectedScenes.first as? UIWindowScene,
let window = scene.windows.first
else { return }
let tapRecognizer = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing))
tapRecognizer.cancelsTouchesInView = false
tapRecognizer.delegate = self
window.addGestureRecognizer(tapRecognizer)
}
}

extension UIApplication: UIGestureRecognizerDelegate {
public func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
return false
}
}
4 changes: 2 additions & 2 deletions Projects/Features/SigninFeature/Sources/SigninView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ struct SigninView: View {
VStack(alignment: .leading, spacing: 8) {
Text("DMS")
.dmsFont(.title(.extraLarge), color: .PrimaryVariant.primary)
.padding(.top, 28)

Text("더 편한 기숙사 생활을 위해")
.dmsFont(.text(.medium), color: .GrayScale.gray6)
}

Spacer()
}
.padding(.top, 24)

VStack(spacing: 72) {
DMSFloatingTextField(
Expand Down Expand Up @@ -96,7 +96,7 @@ struct SigninView: View {
DMSWideButton(text: "로그인", color: .PrimaryVariant.primary) {
viewModel.signinButtonDidTap()
}
.disabled(!viewModel.isSigninButtonEnabled)
.disabled(!viewModel.isSigninEnabled)
.padding(.top, 24)
.frame(maxWidth: .infinity)
.padding(.bottom, 40)
Expand Down
4 changes: 2 additions & 2 deletions Projects/Features/SigninFeature/Sources/SigninViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ final class SigninViewModel: BaseViewModel {
@Published var isOnAutoSignin = true
@Published var isNavigateSignup = false
@Published var isSuccessSignin = false
var isSigninButtonEnabled: Bool {
var isSigninEnabled: Bool {
!id.isEmpty && !password.isEmpty
}

Expand All @@ -19,7 +19,7 @@ final class SigninViewModel: BaseViewModel {
}

func signinButtonDidTap() {
guard isSigninButtonEnabled else { return }
guard isSigninEnabled else { return }
addCancellable(signinUseCase.execute(req: .init(accountID: id, password: password))) { [weak self] _ in
self?.isSuccessSignin = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 false이다.") {
expect { sut.id }.to(beEmpty())
expect { sut.password }.to(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beFalse())
expect { sut.isSigninEnabled }.to(beFalse())
}
}
context("유저가 ID만 입력했다면") {
Expand All @@ -30,7 +30,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 false이다.") {
expect { sut.id }.toNot(beEmpty())
expect { sut.password }.to(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beFalse())
expect { sut.isSigninEnabled }.to(beFalse())
}
}
context("유저가 Password만 입력했다면") {
Expand All @@ -40,7 +40,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 false이다.") {
expect { sut.id }.to(beEmpty())
expect { sut.password }.toNot(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beFalse())
expect { sut.isSigninEnabled }.to(beFalse())
}
}
context("유저가 ID와 Password를 모두 입력했다면") {
Expand All @@ -51,7 +51,7 @@ final class SigninViewModelSpec: QuickSpec {
it("isSigninButtonEnabled은 true이다.") {
expect { sut.id }.toNot(beEmpty())
expect { sut.password }.toNot(beEmpty())
expect { sut.isSigninButtonEnabled }.to(beTrue())
expect { sut.isSigninEnabled }.to(beTrue())
}
}
context("유저가 ID와 Password를 알맞게 입력하고 로그인을 시도한다면") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ struct SchoolCodeView: View {
Text("학교 인증코드 입력")
.dmsFont(.text(.medium), color: .GrayScale.gray6)
}
.padding(.top, 28)

Spacer()
}
.padding(.top, 24)
.padding(.horizontal, 24)

Spacer()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import DomainModule
import NeedleFoundation
import SwiftUI

public protocol SignupEmailAuthCodeVerifyDependency: Dependency {
var sendAuthCodeUseCase: any SendAuthCodeUseCase { get }
var verifyAuthCodeUseCase: any VerifyAuthCodeUseCase { get }
}

public final class SignupEmailAuthCodeVerifyComponent: Component<SignupEmailAuthCodeVerifyDependency> {
public func makeView(signupEmailAuthCodeVerifyParam: SignupEmailAuthCodeVerifyParam) -> some View {
SignupEmailAuthCodeVerifyView(
viewModel: .init(
sendAuthCodeUseCase: dependency.sendAuthCodeUseCase,
verifyAuthCodeUseCase: dependency.verifyAuthCodeUseCase,
signupEmailAuthCodeVerifyParam: signupEmailAuthCodeVerifyParam
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import BaseFeature
import DesignSystem
import SwiftUI

struct SignupEmailAuthCodeVerifyView: View {
@StateObject var viewModel: SignupEmailAuthCodeVerifyViewModel
@Environment(\.dismiss) var dismiss

init(
viewModel: SignupEmailAuthCodeVerifyViewModel
) {
_viewModel = StateObject(wrappedValue: viewModel)
}

var body: some View {
VStack {
HStack {
VStack(alignment: .leading, spacing: 8) {
Text("DMS")
.dmsFont(.title(.extraLarge), color: .PrimaryVariant.primary)

Text("이메일 주소 입력")
.dmsFont(.text(.medium), color: .GrayScale.gray6)
}

Spacer()
}
.padding(.top, 24)

VStack(spacing: 40) {
DMSPassCodeView(codeCount: 6, text: $viewModel.authCode)

Text(viewModel.isErrorOcuured ? viewModel.errorMessage : "이메일로 전송된 인증코드 6자리를 입력해주세요.")
.dmsFont(.text(.small), color: viewModel.isErrorOcuured ? .System.error : .GrayScale.gray5)
}
.padding(.top, 60)

Text(viewModel.timeText)
.dmsFont(.text(.small), color: .PrimaryVariant.primary)
.padding(.top, 10)

Spacer()

DMSButton(text: "인증코드 재발송", style: .underline, color: .GrayScale.gray6) {
viewModel.sendEmailAuthCode()
}

DMSWideButton(text: "인증", color: .PrimaryVariant.primary) {
viewModel.verifyEmailAuthCode()
}
.padding(.top, 32)
.padding(.bottom, 40)
}
.dmsBackButton(dismiss: dismiss)
.padding(.horizontal, 24)
.onAppear {
UIApplication.shared.hideKeyboard()
viewModel.sendEmailAuthCode()
}
.dmsToast(isShowing: $viewModel.isShowingToast, message: viewModel.toastMessage, style: .success)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import BaseFeature
import Combine
import DomainModule
import ErrorModule
import Foundation

final class SignupEmailAuthCodeVerifyViewModel: BaseViewModel {
@Published var authCode = "" {
didSet { isErrorOcuured = false }
}
@Published var timeRemaining = 180
@Published var isShowingToast = false
@Published var toastMessage = ""
@Published var isNavigateSignupID = false
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var timeText: String {
timeRemaining % 60 < 10 ?
"\(timeRemaining/60):0\(timeRemaining%60)" :
"\(timeRemaining/60):\(timeRemaining%60)"
}

private let sendAuthCodeUseCase: any SendAuthCodeUseCase
private let verifyAuthCodeUseCase: any VerifyAuthCodeUseCase
let signupEmailAuthCodeVerifyParam: SignupEmailAuthCodeVerifyParam

init(
sendAuthCodeUseCase: any SendAuthCodeUseCase,
verifyAuthCodeUseCase: any VerifyAuthCodeUseCase,
signupEmailAuthCodeVerifyParam: SignupEmailAuthCodeVerifyParam
) {
self.sendAuthCodeUseCase = sendAuthCodeUseCase
self.verifyAuthCodeUseCase = verifyAuthCodeUseCase
self.signupEmailAuthCodeVerifyParam = signupEmailAuthCodeVerifyParam
super.init()

addCancellable(
timer.setFailureType(to: DmsError.self).eraseToAnyPublisher()
) { [weak self] _ in
guard let self, self.timeRemaining > 0 else { return }
self.timeRemaining -= 1
}
}

func sendEmailAuthCode() {
addCancellable(
sendAuthCodeUseCase.execute(
req: .init(email: signupEmailAuthCodeVerifyParam.email, type: .signup)
)
) { [weak self] _ in
self?.authCode = ""
self?.timeRemaining = 180
self?.toastMessage = "입력하신 이메일로 인증번호가 전송되었습니다."
self?.isShowingToast = true
}
}

func verifyEmailAuthCode() {
addCancellable(
verifyAuthCodeUseCase.execute(
req: .init(email: signupEmailAuthCodeVerifyParam.email, authCode: authCode, type: .signup)
)
) { [weak self] _ in
self?.isNavigateSignupID = true
} onReceiveError: { [weak self] _ in
self?.authCode = ""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

public struct SignupEmailAuthCodeVerifyParam: Equatable {
public init(schoolCode: String, schoolAnswer: String, email: String) {
self.schoolCode = schoolCode
self.schoolAnswer = schoolAnswer
self.email = email
}

public let schoolCode: String
public let schoolAnswer: String
public let email: String
}
Loading

0 comments on commit 0cd556a

Please sign in to comment.