Skip to content

Commit

Permalink
[MBL-1355] Move Login/Signup Logic To Confirm Pledge Details (#2029)
Browse files Browse the repository at this point in the history
* remove unused LoginToutViewModel configuration arguments

* go to login on "continue" tap if logged out

* Only create a checkout Id once the user is logged in.

* remove login logic from Checkout screen

* update LoginToutViewModel Tests

* add new tests to ConfirmDetailsViewModelTests

* Revert "remove unused LoginToutViewModel configuration arguments"

This reverts commit 740240b.

* Revert "update LoginToutViewModel Tests"

This reverts commit f770e59.

* cleanup

* auto continue flow on login
  • Loading branch information
scottkicks authored Apr 9, 2024
1 parent 8e8f683 commit d44f316
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 254 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ final class ConfirmDetailsViewController: UIViewController, MessageBannerViewCon
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}()

private var sessionStartedObserver: Any?

private let viewModel: ConfirmDetailsViewModelType = ConfirmDetailsViewModel()

// MARK: - Lifecycle
Expand Down Expand Up @@ -132,6 +134,10 @@ final class ConfirmDetailsViewController: UIViewController, MessageBannerViewCon
self.viewModel.inputs.viewDidLoad()
}

deinit {
self.sessionStartedObserver.doIfSome(NotificationCenter.default.removeObserver)
}

// MARK: - Configuration

private func configureChildViewControllers() {
Expand Down Expand Up @@ -275,6 +281,17 @@ final class ConfirmDetailsViewController: UIViewController, MessageBannerViewCon
self?.messageBannerViewController?.showBanner(with: .error, message: errorMessage)
}

self.viewModel.outputs.goToLoginSignup
.observeForControllerAction()
.observeValues { [weak self] intent, project, reward in
self?.goToLoginSignup(with: intent, project: project, reward: reward)
}

self.sessionStartedObserver = NotificationCenter.default
.addObserver(forName: .ksr_sessionStarted, object: nil, queue: nil) { [weak self] _ in
self?.viewModel.inputs.userSessionStarted()
}

self.pledgeAmountViewController.view.rac.hidden = self.viewModel.outputs.pledgeAmountViewHidden

self.shippingLocationViewController.view.rac.hidden = self.viewModel.outputs.shippingLocationViewHidden
Expand All @@ -301,6 +318,18 @@ final class ConfirmDetailsViewController: UIViewController, MessageBannerViewCon

// MARK: - Functions

private func goToLoginSignup(with intent: LoginIntent, project: Project, reward: Reward?) {
let loginSignupViewController = LoginToutViewController.configuredWith(
loginIntent: intent,
project: project,
reward: reward
)

let navigationController = UINavigationController(rootViewController: loginSignupViewController)

self.present(navigationController, animated: true)
}

private func goToCheckout(data: PostCampaignCheckoutData) {
let vc = PostCampaignCheckoutViewController.instantiate()
vc.configure(with: data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ final class PostCampaignCheckoutViewController: UIViewController,
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}()

private var sessionStartedObserver: Any?

private let viewModel: PostCampaignCheckoutViewModelType = PostCampaignCheckoutViewModel()

// MARK: - Lifecycle
Expand Down Expand Up @@ -97,7 +95,6 @@ final class PostCampaignCheckoutViewController: UIViewController,

deinit {
self.hideProcessingView()
self.sessionStartedObserver.doIfSome(NotificationCenter.default.removeObserver)
}

// MARK: - Configuration
Expand Down Expand Up @@ -210,12 +207,6 @@ final class PostCampaignCheckoutViewController: UIViewController,
self?.paymentMethodsViewController.configure(with: value)
}

self.viewModel.outputs.goToLoginSignup
.observeForControllerAction()
.observeValues { [weak self] intent, project, reward in
self?.goToLoginSignup(with: intent, project: project, reward: reward)
}

self.viewModel.outputs.showWebHelp
.observeForControllerAction()
.observeValues { [weak self] helpType in
Expand All @@ -237,13 +228,6 @@ final class PostCampaignCheckoutViewController: UIViewController,
self?.messageBannerViewController?.showBanner(with: .error, message: errorMessage)
}

self.sessionStartedObserver = NotificationCenter.default
.addObserver(forName: .ksr_sessionStarted, object: nil, queue: nil) { [weak self] _ in
self?.viewModel.inputs.userSessionStarted()
}

self.paymentMethodsViewController.view.rac.hidden = self.viewModel.outputs.paymentMethodsViewHidden

self.viewModel.outputs.configureStripeIntegration
.observeForUI()
.observeValues { merchantIdentifier, publishableKey in
Expand Down Expand Up @@ -282,18 +266,6 @@ final class PostCampaignCheckoutViewController: UIViewController,

// MARK: - Functions

private func goToLoginSignup(with intent: LoginIntent, project: Project, reward: Reward?) {
let loginSignupViewController = LoginToutViewController.configuredWith(
loginIntent: intent,
project: project,
reward: reward
)

let navigationController = UINavigationController(rootViewController: loginSignupViewController)

self.present(navigationController, animated: true)
}

private func confirmPayment(with validation: PaymentSourceValidation) {
guard validation.requiresConfirmation else {
// Short circuit for payment intents that have already been validated
Expand Down Expand Up @@ -353,7 +325,6 @@ extension PostCampaignCheckoutViewController: PledgeDisclaimerViewDelegate {
extension PostCampaignCheckoutViewController: PledgeViewCTAContainerViewDelegate {
func goToLoginSignup() {
self.paymentMethodsViewController.cancelModalPresentation(true)
self.viewModel.inputs.goToLoginSignupTapped()
}

func applePayButtonTapped() {
Expand Down
37 changes: 32 additions & 5 deletions Library/ViewModels/ConfirmDetailsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public protocol ConfirmDetailsViewModelInputs {
func continueCTATapped()
func pledgeAmountViewControllerDidUpdate(with data: PledgeAmountData)
func shippingRuleSelected(_ shippingRule: ShippingRule)
func userSessionStarted()
func viewDidLoad()
}

Expand All @@ -24,13 +25,14 @@ public protocol ConfirmDetailsViewModelOutputs {
var configureShippingLocationViewWithData: Signal<PledgeShippingLocationViewData, Never> { get }
var configureShippingSummaryViewWithData: Signal<PledgeShippingSummaryViewData, Never> { get }
var createCheckoutSuccess: Signal<PostCampaignCheckoutData, Never> { get }
var showErrorBannerWithMessage: Signal<String, Never> { get }
var goToLoginSignup: Signal<(LoginIntent, Project, Reward?), Never> { get }
var localPickupViewHidden: Signal<Bool, Never> { get }
var pledgeAmountViewHidden: Signal<Bool, Never> { get }
var pledgeRewardsSummaryViewHidden: Signal<Bool, Never> { get }
var pledgeSummaryViewHidden: Signal<Bool, Never> { get }
var shippingLocationViewHidden: Signal<Bool, Never> { get }
var shippingSummaryViewHidden: Signal<Bool, Never> { get }
var showErrorBannerWithMessage: Signal<String, Never> { get }
}

public protocol ConfirmDetailsViewModelType {
Expand Down Expand Up @@ -58,6 +60,18 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail

let backing = project.map { $0.personalization.backing }.skipNil()

let isLoggedIn = Signal.merge(initialData.ignoreValues(), self.userSessionStartedSignal)
.map { _ in AppEnvironment.current.currentUser }
.map(isNotNil)

self.goToLoginSignup = Signal.combineLatest(isLoggedIn, initialData)
.takeWhen(self.continueCTATappedProperty.signal)
.filter { isLoggedIn, _ in isLoggedIn == false }
.map { _, data in
let baseReward = data.rewards.first
return (LoginIntent.backProject, data.project, baseReward)
}

// MARK: Pledge Amount

self.pledgeAmountViewHidden = context.map { $0.pledgeAmountViewHidden }
Expand Down Expand Up @@ -319,9 +333,16 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
refTag
)

let createCheckoutEvents = pledgeDetailsData
.takeWhen(self.continueCTATappedProperty.signal)
.map { project, rewards, selectedQuantities, pledgeTotal, refTag in
let isLoggedInAndContinueButtonTapped = Signal.merge(
self.continueCTATappedProperty.signal,
self.userSessionStartedSignal
)

let createCheckoutEvents = Signal.combineLatest(isLoggedIn, pledgeDetailsData)
.takeWhen(isLoggedInAndContinueButtonTapped)
.filter { isLoggedIn, _ in isLoggedIn }
.map { _, pledgeDetailsData in
let (project, rewards, selectedQuantities, pledgeTotal, refTag) = pledgeDetailsData
let rewardsIDs: [String] = rewards.first?.isNoReward == true
? []
: rewards.flatMap { reward -> [String] in
Expand Down Expand Up @@ -429,6 +450,11 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
self.submitButtonTappedObserver.send(value: ())
}

private let (userSessionStartedSignal, userSessionStartedObserver) = Signal<Void, Never>.pipe()
public func userSessionStarted() {
self.userSessionStartedObserver.send(value: ())
}

private let viewDidLoadProperty = MutableProperty(())
public func viewDidLoad() {
self.viewDidLoadProperty.value = ()
Expand All @@ -448,13 +474,14 @@ public class ConfirmDetailsViewModel: ConfirmDetailsViewModelType, ConfirmDetail
public let configureShippingLocationViewWithData: Signal<PledgeShippingLocationViewData, Never>
public let configureShippingSummaryViewWithData: Signal<PledgeShippingSummaryViewData, Never>
public let createCheckoutSuccess: Signal<PostCampaignCheckoutData, Never>
public let showErrorBannerWithMessage: Signal<String, Never>
public let goToLoginSignup: Signal<(LoginIntent, Project, Reward?), Never>
public let localPickupViewHidden: Signal<Bool, Never>
public let pledgeAmountViewHidden: Signal<Bool, Never>
public let pledgeRewardsSummaryViewHidden: Signal<Bool, Never>
public let pledgeSummaryViewHidden: Signal<Bool, Never>
public let shippingLocationViewHidden: Signal<Bool, Never>
public let shippingSummaryViewHidden: Signal<Bool, Never>
public let showErrorBannerWithMessage: Signal<String, Never>

public var inputs: ConfirmDetailsViewModelInputs { return self }
public var outputs: ConfirmDetailsViewModelOutputs { return self }
Expand Down
75 changes: 75 additions & 0 deletions Library/ViewModels/ConfirmDetailsViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ final class ConfirmDetailsViewModelTests: TestCase {

private let createCheckoutSuccess = TestObserver<PostCampaignCheckoutData, Never>()

private let goToLoginSignup = TestObserver<(LoginIntent, Project, Reward?), Never>()

private let localPickupViewHidden = TestObserver<Bool, Never>()
private let pledgeAmountViewHidden = TestObserver<Bool, Never>()
private let shippingLocationViewHidden = TestObserver<Bool, Never>()
Expand Down Expand Up @@ -57,6 +59,8 @@ final class ConfirmDetailsViewModelTests: TestCase {

self.vm.outputs.createCheckoutSuccess.observe(self.createCheckoutSuccess.observer)

self.vm.outputs.goToLoginSignup.observe(self.goToLoginSignup.observer)

self.vm.outputs.localPickupViewHidden.observe(self.localPickupViewHidden.observer)
self.vm.outputs.pledgeAmountViewHidden.observe(self.pledgeAmountViewHidden.observer)
self.vm.outputs.shippingLocationViewHidden.observe(self.shippingLocationViewHidden.observer)
Expand All @@ -65,6 +69,77 @@ final class ConfirmDetailsViewModelTests: TestCase {
self.vm.outputs.showErrorBannerWithMessage.observe(self.showErrorBannerWithMessage.observer)
}

// MARK: - Login/Signup

func testGoToLoginSignup_emitsWhenLoggedOut_createCheckoutSuccessDoesNotEmit() {
withEnvironment(currentUser: nil) {
let data = PledgeViewData(
project: Project.template,
rewards: [Reward.template],
selectedQuantities: [Reward.template.id: 1],
selectedLocationId: nil,
refTag: .projectPage,
context: .latePledge
)

self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.vm.inputs.continueCTATapped()

self.goToLoginSignup.assertDidEmitValue()
self.createCheckoutSuccess.assertDidNotEmitValue()
}
}

func testGoToLoginSignup_doesNotEmitWhenLoggedIn_createCheckoutIsSuccessful() {
let expectedId = "Q2hlY2tvdXQtMTk4MzM2NjQ2"
let createCheckout = CreateCheckoutEnvelope.Checkout(id: expectedId, paymentUrl: "paymentUrl")
let mockService = MockService(
createCheckoutResult:
Result.success(CreateCheckoutEnvelope(checkout: createCheckout))
)

withEnvironment(apiService: mockService, currentUser: User.template) {
let data = PledgeViewData(
project: Project.template,
rewards: [Reward.template],
selectedQuantities: [Reward.template.id: 1],
selectedLocationId: nil,
refTag: .projectPage,
context: .latePledge
)

self.vm.inputs.configure(with: data)
self.vm.inputs.viewDidLoad()

self.vm.inputs.userSessionStarted()

let expectedShipping = PledgeShippingSummaryViewData(
locationName: "Los Angeles, CA",
omitUSCurrencyCode: true,
projectCountry: .us,
total: 3
)
self.vm.inputs.shippingRuleSelected(
ShippingRule(cost: expectedShipping.total, id: nil, location: .losAngeles)
)

let expectedBonus = 5.0
self.vm.inputs.pledgeAmountViewControllerDidUpdate(
with: (amount: expectedBonus, min: 0, max: 100, isValid: true)
)

self.vm.inputs.continueCTATapped()

self.scheduler.run()

self.goToLoginSignup.assertDidNotEmitValue()

self.createCheckoutSuccess.assertDidEmitValue()
}
}

func testPledgeContext_LoggedIn() {
let mockService = MockService(serverConfig: ServerConfig.staging)

Expand Down
Loading

0 comments on commit d44f316

Please sign in to comment.