Skip to content
Open
Changes from all commits
Commits
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily for this PR, but we should move this logic either up to the model or to a helper so can be tested that we're rendering the correct buttons for all order status cases with feature flag on/off.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct POSOrderDetailsView: View {
@Environment(\.siteTimezone) private var siteTimezone
@Environment(POSOrderListModel.self) private var orderListModel
@Environment(\.posAnalytics) private var analytics
@Environment(\.posFeatureFlags) private var featureFlags
@State private var isShowingEmailReceiptView: Bool = false

private var shouldShowBackButton: Bool {
Expand All @@ -32,9 +33,7 @@ struct POSOrderDetailsView: View {
title: POSOrderListView.Localization.orderTitle(order.number),
backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack) : nil,
trailingContent: {
if actions.isNotEmpty {
actionsSection(actions)
}
actionsSection(actions: availableActions)
},
bottomContent: {
headerBottomContent(for: order)
Expand Down Expand Up @@ -377,59 +376,98 @@ private extension POSOrderDetailsView {

// MARK: - Actions
private extension POSOrderDetailsView {
enum POSOrderDetailsAction: Identifiable, CaseIterable {
enum POSAction: Identifiable, CaseIterable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this rename necessary? It makes the POSAction too generic for what is doing, I'd revert it to be POSOrderDetailsAction or something specific to actions to this view. Specially when reaching to .priority or .isAvailable() props they lose readability unless we look into the implementation.

case issueRefund
case emailReceipt

var id: String { title }

var title: String {
switch self {
case .emailReceipt:
Localization.emailReceiptActionTitle
case .issueRefund: Localization.issueRefundActionTitle
case .emailReceipt: Localization.emailReceiptActionTitle
}
}

var accessibilityHint: String {
switch self {
case .issueRefund: Localization.issueRefundAccessibilityHint
case .emailReceipt: Localization.emailReceiptAccessibilityHint
}
}

var priority: Int {
switch self {
case .issueRefund: 100
case .emailReceipt: 50
}
}

func available(for order: POSOrder) -> Bool {
func isAvailable(for order: POSOrder, flags: POSFeatureFlagProviding) -> Bool {
guard order.status == .completed else { return false }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check breaks the current behavior of showing the "send receipt" button for refunded orders, since will return early if the order is not completed. It should show like this:

Simulator Screenshot - iPad mini (A17 Pro) - US store - 2025-12-03 at 07 47 52

switch self {
case .issueRefund:
return flags.isFeatureFlagEnabled(.pointOfSaleRefundsi1)
case .emailReceipt:
order.status == .completed
return true
}
}
}

func handler(for action: POSAction) -> @MainActor () -> Void {
switch action {
case .emailReceipt:
return {
analytics.track(event: WooAnalyticsEvent.PointOfSale.orderDetailsEmailReceiptTapped())
isShowingEmailReceiptView = true
}
case .issueRefund:
return { }
}
}

var actions: [POSOrderDetailsAction] {
POSOrderDetailsAction.allCases.filter { $0.available(for: order) }
var availableActions: [POSAction] {
POSAction.allCases
.filter { $0.isAvailable(for: order, flags: featureFlags) }
.sorted { $0.priority > $1.priority }
}

@ViewBuilder
func actionsSection(_ actions: [POSOrderDetailsAction]) -> some View {
VStack {
HStack {
ForEach(actions) { action in
Button(action: {
switch action {
case .emailReceipt:
analytics.track(event: WooAnalyticsEvent.PointOfSale.orderDetailsEmailReceiptTapped())
isShowingEmailReceiptView = true
func actionsSection(actions: [POSAction]) -> some View {
if actions.isEmpty {
EmptyView()
} else {
HStack(spacing: POSSpacing.large) {
let primary = actions[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be explicit on which action is triggering rather than rely on array order, using the specific enum case or others.

Button(primary.title, action: handler(for: primary))
.buttonStyle(POSFilledButtonStyle(size: .extraSmall))
.accessibilityHint(primary.accessibilityHint)
.lineLimit(1)
.minimumScaleFactor(0.5)

let overflow = actions.dropFirst()
if !overflow.isEmpty {
Menu {
ForEach(Array(overflow)) { action in
Comment on lines +448 to +451
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to understand this bit, could you clarify what it does? Why are we dropping the first result when rendering the menu options?

Button(action.title, action: handler(for: action))
.accessibilityHint(action.accessibilityHint)
}
}) {
Text(Localization.emailReceiptActionTitle)
.lineLimit(1)
.minimumScaleFactor(0.5)
} label: {
Image(systemName: "ellipsis")
.font(.posBodyLargeBold)
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
.foregroundColor(.posOnSurface)
.padding(POSPadding.small)
}
.buttonStyle(POSFilledButtonStyle(size: .extraSmall))
.accessibilityHint(accessibilityHint(for: action))
.menuIndicator(.hidden)
}
}
Spacer()
}
}

private func accessibilityHint(for action: POSOrderDetailsAction) -> String {
switch action {
case .emailReceipt:
return Localization.emailReceiptAccessibilityHint
}
func emailReceiptAction() {
analytics.track(event: WooAnalyticsEvent.PointOfSale.orderDetailsEmailReceiptTapped())
isShowingEmailReceiptView = true
}
}

Expand Down Expand Up @@ -529,6 +567,24 @@ private enum Localization {
comment: "Accessibility hint for email receipt button on order details view"
)

static let issueRefundActionTitle = NSLocalizedString(
"pos.orderDetailsView.issueRefundAction.title",
value: "Issue refund",
comment: "Primary action button to start issuing a refund on the order details view"
)

static let issueRefundAccessibilityHint = NSLocalizedString(
"pos.orderDetailsView.issueRefundAction.accessibilityHint",
value: "Start refund flow for this order",
comment: "Accessibility hint for issue refund button"
)

static let moreActionsA11yLabel = NSLocalizedString(
"pos.orderDetailsView.moreActions.label",
value: "More actions",
comment: "Accessibility label for the overflow actions menu button (three dots)"
)

static func headerBottomContentAccessibilityLabel(date: String, email: String?, status: String) -> String {
let baseFormat = NSLocalizedString(
"pos.orderDetailsView.headerBottomContent.accessibilityLabel",
Expand Down
Loading