Skip to content

[Woo POS] Coupons: List - Enable Coupons - Wrap Up (Designer UI and Copy) #15505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import enum Yosemite.PointOfSaleCouponServiceError
import protocol Yosemite.PointOfSaleItemServiceProtocol
import protocol Yosemite.PointOfSaleCouponServiceProtocol

@available(iOS 17.0, *)
protocol PointOfSaleCouponsControllerProtocol: PointOfSaleItemsControllerProtocol {
/// Enables coupons in store settings
/// Returns true if coupons enabled
func enableCoupons() async
}

@available(iOS 17.0, *)
@Observable final class PointOfSaleCouponsController: PointOfSaleCouponsControllerProtocol {
var itemsViewState: ItemsViewState = ItemsViewState(containerState: .content,
Expand Down Expand Up @@ -55,10 +62,11 @@ import protocol Yosemite.PointOfSaleCouponServiceProtocol
itemsViewState.itemsStack.root = .loading([])
do {
try await couponProvider.enableCoupons()
await loadItems(base: .root)
} catch {
// TODO: WOOMOB-267
// Handle error when failed to enable, and allow retry action
debugPrint(error)
if let couponError = error as? PointOfSaleCouponServiceError {
setCouponsErrorViewState(couponError)
}
}
}
}
Expand Down Expand Up @@ -129,6 +137,8 @@ private extension PointOfSaleCouponsController {
stackState = ItemsStackState(root: .error(.errorOnLoadingCoupons), itemStates: [:])
case .couponsDisabled:
stackState = ItemsStackState(root: .error(.errorCouponsDisabled), itemStates: [:])
case .couponsEnablingError:
stackState = ItemsStackState(root: .error(.errorOnEnablingCoupons), itemStates: [:])
}

itemsViewState = ItemsViewState(containerState: containerState, itemsStack: stackState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@ protocol PointOfSaleItemsControllerProtocol {
func loadNextItems(base: ItemListBaseItem) async
}

@available(iOS 17.0, *)
protocol PointOfSaleCouponsControllerProtocol: PointOfSaleItemsControllerProtocol {
/// Enables coupons in store settings, if needed
func enableCoupons() async
}

@available(iOS 17.0, *)
protocol PointOfSaleSearchingItemsControllerProtocol: PointOfSaleItemsControllerProtocol {
/// Searches for items
Expand Down
36 changes: 36 additions & 0 deletions WooCommerce/Classes/POS/Models/ItemListState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,42 @@ enum ItemListState {
return false
}
}

var isLoaded: Bool {
switch self {
case .loaded:
return true
default:
return false
}
}

var isInlineError: Bool {
switch self {
case .inlineError:
return true
default:
return false
}
}

var isError: Bool {
switch self {
case .error:
return true
default:
return false
}
}

var isEmpty: Bool {
switch self {
case .empty:
return true
default:
return false
}
}
}

extension ItemListState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,6 @@ extension PointOfSaleAggregateModel {
}
}

// MARK: - Coupons
@available(iOS 17.0, *)
extension PointOfSaleAggregateModel {
func enableCoupons() async {
await couponsController.enableCoupons()
}
}

// MARK: - Track events
@available(iOS 17.0, *)
private extension PointOfSaleAggregateModel {
Expand Down
24 changes: 24 additions & 0 deletions WooCommerce/Classes/POS/Models/PointOfSaleErrorState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ struct PointOfSaleErrorState: Equatable {
buttonText: Constants.loadingCouponsErrorRetry)
}

static var errorOnEnablingCoupons: Self {
PointOfSaleErrorState(
errorType: .couponsDisabled,
title: Constants.enablingCouponsErrorTitle,
subtitle: Constants.enablingCouponsErrorSubtitle,
buttonText: Constants.enablingCouponsErrorRetry)
}

static var errorCouponsDisabled: Self {
PointOfSaleErrorState(
errorType: .couponsDisabled,
Expand Down Expand Up @@ -161,5 +169,21 @@ struct PointOfSaleErrorState: Equatable {
comment: "Text for the button appearing on the item list screen when there's an error loading a page of " +
"variations after the first. Shown inline with the previously loaded items above."
)

static let enablingCouponsErrorTitle = NSLocalizedString(
"pos.itemList.enablingCouponsErrorTitle",
value: "Error enabling coupons",
comment: "Title appearing on the coupon list screen when there's an error enabling coupons setting in the store."
)
static let enablingCouponsErrorSubtitle = NSLocalizedString(
"pos.itemList.enablingCouponsErrorSubtitle",
value: "Give it another go?",
comment: "Subtitle appearing on the coupon list screen when there's an error enabling coupons setting in the store."
)
static let enablingCouponsErrorRetry = NSLocalizedString(
"pos.itemList.enablingCouponsErrorRetry",
value: "Retry",
comment: "Text of the button appearing on the coupon list screen when there's an error enabling coupons setting in the store.."
)
}
}
6 changes: 3 additions & 3 deletions WooCommerce/Classes/POS/Presentation/ItemListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ private extension ItemListView {

Spacer()

if case .coupons = selectedItemType {
if case .coupons = selectedItemType, itemListState.isLoaded || itemListState.isEmpty {
Button(action: {
showCouponCreationModal = true
}, label: {
Expand Down Expand Up @@ -258,10 +258,10 @@ private extension ItemListView {
@ViewBuilder
func errorView(_ errorState: PointOfSaleErrorState) -> some View {
switch errorState {
case .errorCouponsDisabled:
case .errorCouponsDisabled, .errorOnEnablingCoupons:
PointOfSaleItemListErrorView(error: errorState, onAction: {
Task {
await posModel.enableCoupons()
await posModel.couponsController.enableCoupons()
}
})
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,37 @@ struct PointOfSaleCouponsControllerTests {
// Then
#expect(sut.itemsViewState == expectedViewState)
}

@available(iOS 17.0, *)
@Test func enableCoupons_sets_error_state_when_fails() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
couponProvider.errorToThrow = .couponsEnablingError
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)

// When
await sut.enableCoupons()

// Then
guard case .error = sut.itemsViewState.itemsStack.root else {
Issue.record("Expected error state")
return
}
}

@available(iOS 17.0, *)
@Test func enableCoupons_loads_items_when_successful() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)
let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons()
let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:])
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)

// When
_ = await sut.enableCoupons()

// Then
#expect(sut.itemsViewState == expectedViewState)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

@available(iOS 17.0, *)
final class MockPointOfSaleCouponsController: PointOfSaleCouponsControllerProtocol {
func enableCoupons() async { }
var loadItemsCalled = false
var loadItemsBase: ItemListBaseItem?

var itemsViewState: ItemsViewState = .init(containerState: .content,
itemsStack: .init(root: .empty, itemStates: [:]))

func loadItems(base: ItemListBaseItem) async { }
func loadItems(base: ItemListBaseItem) async {
loadItemsCalled = true
loadItemsBase = base
}

func refreshItems(base: ItemListBaseItem) async { }

func loadNextItems(base: ItemListBaseItem) async { }
func enableCoupons() async { }
}
7 changes: 4 additions & 3 deletions Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Storage
public enum PointOfSaleCouponServiceError: Error {
case couponsLoadingError
case couponsDisabled
case couponsEnablingError
}

public protocol PointOfSaleCouponServiceProtocol {
Expand Down Expand Up @@ -79,13 +80,13 @@ public final class PointOfSaleCouponService: PointOfSaleCouponServiceProtocol {

@MainActor
public func enableCoupons() async throws {
_ = await withCheckedContinuation { continuation in
return try await withCheckedThrowingContinuation { continuation in
settingsStoreMethods.enableCouponSetting(siteID: siteID) { result in
switch result {
case .success:
continuation.resume(returning: true)
continuation.resume(returning: ())
case .failure:
continuation.resume(returning: false)
continuation.resume(throwing: PointOfSaleCouponServiceError.couponsEnablingError)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Yosemite/YosemiteTests/Tools/POS/POSOrderServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ struct POSOrderServiceTests {

// Then
let createdOrderItems = try #require(mockOrdersRemote.spyCreatePOSOrder?.items)
#expect(createdOrderItems.contains(where: { item in
#expect(createdOrderItems.contains(where: { (item: OrderItem) -> Bool in
item.productID == 100 && item.quantity == 2
}))
#expect(createdOrderItems.contains(where: { item in
#expect(createdOrderItems.contains(where: { (item: OrderItem) -> Bool in
item.productID == 102 && item.quantity == 1
}))
}
Expand Down
Loading