-
Notifications
You must be signed in to change notification settings - Fork 121
[POS as a tab i2] Update POS ineligible UI with detailed text and design updates #15859
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
Changes from all commits
fd5a6e0
c186f7f
1f63381
a2b5591
ab94a2c
35374aa
5989d55
9f7fe65
33eb215
f95efc5
8ae5ba5
156eefe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,129 +2,259 @@ import SwiftUI | |
|
|
||
| /// A view that displays when the Point of Sale (POS) feature is not available for the current store. | ||
| /// Shows the specific reason why POS is ineligible and provides a button to re-check eligibility. | ||
| @available(iOS 17.0, *) | ||
| struct POSIneligibleView: View { | ||
| let reason: POSIneligibleReason | ||
| let onRefresh: () async throws -> Void | ||
| @Environment(\.dismiss) private var dismiss | ||
| @State private var isLoading: Bool = false | ||
|
|
||
| var body: some View { | ||
| VStack(spacing: POSSpacing.large) { | ||
| HStack { | ||
| Spacer() | ||
| Button { | ||
| dismiss() | ||
| } label: { | ||
| Text(Image(systemName: "xmark")) | ||
| .font(POSFontStyle.posButtonSymbolLarge.font()) | ||
| } | ||
| .foregroundColor(Color.posOnSurfaceVariantLowest) | ||
| } | ||
|
|
||
| VStack(spacing: POSSpacing.none) { | ||
| Spacer() | ||
|
|
||
| VStack(spacing: POSSpacing.medium) { | ||
| VStack(alignment: .center, spacing: POSSpacing.none) { | ||
| Image(PointOfSaleAssets.exclamationMark.imageName) | ||
| .resizable() | ||
| .frame(width: POSErrorAndAlertIconSize.large.dimension, | ||
| height: POSErrorAndAlertIconSize.large.dimension) | ||
|
|
||
| Text(reasonText) | ||
| .font(POSFontStyle.posHeadingBold.font()) | ||
| .multilineTextAlignment(.center) | ||
| .foregroundColor(Color.posOnSurface) | ||
|
|
||
| Button { | ||
| Task { @MainActor in | ||
| do { | ||
| isLoading = true | ||
| try await onRefresh() | ||
| isLoading = false | ||
| } catch { | ||
| // TODO-jc: handle error if needed, e.g., show an error message | ||
| print("Error refreshing eligibility: \(error)") | ||
| isLoading = false | ||
| Spacer() | ||
| .frame(height: POSSpacing.medium) | ||
|
|
||
| VStack(spacing: POSSpacing.small) { | ||
| Text(Localization.title) | ||
| .font(POSFontStyle.posHeadingBold.font()) | ||
| .multilineTextAlignment(.center) | ||
| .foregroundColor(Color.posOnSurface) | ||
|
|
||
| Text(suggestionText) | ||
| .font(POSFontStyle.posBodyLargeRegular().font()) | ||
| .multilineTextAlignment(.center) | ||
| .foregroundColor(Color.posOnSurface) | ||
| } | ||
| .containerRelativeFrame(.horizontal) { length, _ in | ||
| max(length * 0.5, 300) | ||
| } | ||
|
|
||
| Spacer() | ||
| .frame(height: POSSpacing.large) | ||
|
|
||
| VStack(spacing: POSSpacing.medium) { | ||
| Button { | ||
| Task { @MainActor in | ||
| do { | ||
| isLoading = true | ||
| try await onRefresh() | ||
| isLoading = false | ||
| } catch { | ||
| // TODO: WOOMOB-720 - handle error if needed, e.g., show an error message | ||
| DDLogError("Error refreshing eligibility: \(error)") | ||
| isLoading = false | ||
| } | ||
| } | ||
| } label: { | ||
| Text(Localization.refreshEligibility) | ||
| } | ||
| } label: { | ||
| Text(Localization.refreshEligibility) | ||
| .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) | ||
|
|
||
| Button { | ||
| dismiss() | ||
| } label: { | ||
| Text(Localization.dismiss) | ||
| } | ||
| .buttonStyle(POSOutlinedButtonStyle(size: .normal)) | ||
| } | ||
| .containerRelativeFrame(.horizontal) { length, _ in | ||
| max(length * 0.5 - 132, 300) | ||
| } | ||
| .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) | ||
| } | ||
|
|
||
| Spacer() | ||
| } | ||
| .padding(POSPadding.large) | ||
| } | ||
|
|
||
| private var reasonText: String { | ||
| private var suggestionText: String { | ||
| switch reason { | ||
| case .notTablet: | ||
| return NSLocalizedString("pos.ineligible.reason.notTablet", | ||
| value: "POS is only available on iPad.", | ||
| comment: "Ineligible reason: not a tablet") | ||
| return NSLocalizedString("pos.ineligible.suggestion.notTablet", | ||
| value: "Please use a tablet to access POS features.", | ||
| comment: "Suggestion for not tablet: use iPad") | ||
| case .unsupportedIOSVersion: | ||
| return NSLocalizedString("pos.ineligible.reason.unsupportedIOSVersion", | ||
| value: "POS requires a newer version of iOS 17 and above.", | ||
| comment: "Ineligible reason: iOS version too low") | ||
| case .unsupportedWooCommerceVersion: | ||
| return NSLocalizedString("pos.ineligible.reason.unsupportedWooCommerceVersion", | ||
| value: "Please update WooCommerce plugin to use POS.", | ||
| comment: "Ineligible reason: WooCommerce version too low") | ||
| return NSLocalizedString("pos.ineligible.suggestion.unsupportedIOSVersion", | ||
| value: "Point of Sale requires iOS 17 or later. Please update your device to iOS 17+ to use this feature.", | ||
| comment: "Suggestion for unsupported iOS version: update iOS") | ||
| case let .unsupportedWooCommerceVersion(minimumVersion): | ||
| let format = NSLocalizedString("pos.ineligible.suggestion.unsupportedWooCommerceVersion", | ||
| value: "Your WooCommerce version is not supported. " + | ||
| "The POS system requires WooCommerce version %1$@ or above. Please update WooCommerce to the latest version.", | ||
| comment: "Suggestion for unsupported WooCommerce version: update plugin. " + | ||
| "%1$@ is a placeholder for the minimum required version.") | ||
| return String.localizedStringWithFormat(format, minimumVersion) | ||
| case .wooCommercePluginNotFound: | ||
| return NSLocalizedString("pos.ineligible.reason.wooCommercePluginNotFound", | ||
| value: "WooCommerce plugin not found.", | ||
| comment: "Ineligible reason: plugin missing") | ||
| return NSLocalizedString("pos.ineligible.suggestion.wooCommercePluginNotFound", | ||
| value: "Install and activate the WooCommerce plugin from your WordPress admin.", | ||
| comment: "Suggestion for missing WooCommerce plugin: install plugin") | ||
| case .featureSwitchDisabled: | ||
| return NSLocalizedString("pos.ineligible.reason.featureSwitchDisabled", | ||
| value: "POS feature is not enabled for your store.", | ||
| comment: "Ineligible reason: feature switch off") | ||
| return NSLocalizedString("pos.ineligible.suggestion.featureSwitchDisabled", | ||
| value: "Point of Sale must be enabled to proceed. " + | ||
| "Please enable the POS feature from your WordPress admin under WooCommerce settings > Advanced > Features.", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there an API we can use to just do this when people tap a button on this screen?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, there is pdfdoF-6VP-p2#endpoint, I created an issue to integrate with the API in WOOMOB-759. |
||
| comment: "Suggestion for disabled feature switch: enable feature in WooCommerce settings") | ||
| case .featureSwitchSyncFailure: | ||
| return NSLocalizedString("pos.ineligible.reason.featureSwitchSyncFailure", | ||
| value: "Could not verify POS feature status.", | ||
| comment: "Ineligible reason: feature switch sync failed") | ||
| case .unsupportedCountry: | ||
| return NSLocalizedString("pos.ineligible.reason.unsupportedCountry", | ||
| value: "POS is not available in your country.", | ||
| comment: "Ineligible reason: country not supported") | ||
| case .unsupportedCurrency: | ||
| return NSLocalizedString("pos.ineligible.reason.unsupportedCurrency", | ||
| value: "POS is not available for your store's currency.", | ||
| comment: "Ineligible reason: currency not supported") | ||
| return NSLocalizedString("pos.ineligible.suggestion.featureSwitchSyncFailure", | ||
| value: "Try relaunching the app or check your internet connection and try again.", | ||
| comment: "Suggestion for feature switch sync failure: relaunch or check connection") | ||
| case let .unsupportedCountry(supportedCountries): | ||
| let countryNames = supportedCountries.map { $0.readableCountry } | ||
| let formattedCountryList = ListFormatter.localizedString(byJoining: countryNames) | ||
| let format = NSLocalizedString( | ||
| "pos.ineligible.suggestion.unsupportedCountry", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see the tab in unsupported countries yet; I'm assuming that's just not changed yet 😊
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the previous comment https://github.com/woocommerce/woocommerce-ios/pull/15859/files#r2182744134, it's because the ineligible enum includes the cases that make the POS tab invisible. I will handle that in the separate issue linked there. |
||
| value: "POS is currently only available in %1$@. Check back later for availability in your region.", | ||
| comment: "Suggestion for unsupported country with list of supported countries. " + | ||
| "%1$@ is a placeholder for the localized list of supported country names." | ||
| ) | ||
| return String.localizedStringWithFormat(format, formattedCountryList) | ||
| case let .unsupportedCurrency(supportedCurrencies): | ||
| let currencyList = supportedCurrencies.map { $0.rawValue } | ||
| let formattedCurrencyList = ListFormatter.localizedString(byJoining: currencyList) | ||
| let format = NSLocalizedString( | ||
| "pos.ineligible.suggestion.unsupportedCurrency", | ||
| value: "The POS system is not available for your store’s currency. It currently supports only %1$@. " + | ||
| "Please check your store currency settings or contact support for assistance.", | ||
| comment: "Suggestion for unsupported currency with list of supported currencies. " + | ||
| "%1$@ is a placeholder for the localized list of supported currency codes." | ||
| ) | ||
| return String.localizedStringWithFormat(format, formattedCurrencyList) | ||
| case .siteSettingsNotAvailable: | ||
| return NSLocalizedString("pos.ineligible.reason.siteSettingsNotAvailable", | ||
| value: "Unable to load store settings for POS.", | ||
| comment: "Ineligible reason: site settings unavailable") | ||
| return NSLocalizedString("pos.ineligible.suggestion.siteSettingsNotAvailable", | ||
| value: "Check your internet connection and try relaunching the app. If the issue persists, please contact support.", | ||
| comment: "Suggestion for site settings unavailable: check connection or contact support") | ||
| case .featureFlagDisabled: | ||
| return NSLocalizedString("pos.ineligible.reason.featureFlagDisabled", | ||
| value: "POS feature is currently disabled.", | ||
| comment: "Ineligible reason: feature flag disabled") | ||
| return NSLocalizedString("pos.ineligible.suggestion.featureFlagDisabled", | ||
| value: "POS is currently disabled.", | ||
| comment: "Suggestion for disabled feature flag: notify that POS is disabled remotely") | ||
| case .selfDeallocated: | ||
| return Localization.defaultReason | ||
| return NSLocalizedString("pos.ineligible.suggestion.selfDeallocated", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😬 hope we don't need this one! |
||
| value: "Try relaunching the app to resolve this issue.", | ||
| comment: "Suggestion for self deallocated: relaunch") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @available(iOS 17.0, *) | ||
| private extension POSIneligibleView { | ||
| enum Localization { | ||
| static let title = NSLocalizedString( | ||
| "pos.ineligible.title", | ||
| value: "Unable to load", | ||
| comment: "Title shown in POS ineligible view" | ||
| ) | ||
|
|
||
| static let refreshEligibility = NSLocalizedString( | ||
| "pos.ineligible.refresh.button.title", | ||
| value: "Check Eligibility Again", | ||
| value: "Retry", | ||
| comment: "Button title to refresh POS eligibility check" | ||
| ) | ||
|
|
||
| /// Default message shown when POS eligibility reason is not available. | ||
| static let defaultReason = NSLocalizedString( | ||
| "pos.ineligible.default.reason", | ||
| value: "Your store is not eligible for POS at this time.", | ||
| comment: "Default message shown when POS eligibility reason is not available" | ||
| static let dismiss = NSLocalizedString( | ||
| "pos.ineligible.dismiss.button.title", | ||
| value: "Exit POS", | ||
| comment: "Button title to dismiss POS ineligible view" | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #if DEBUG | ||
|
|
||
| #Preview("Unsupported currency") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .unsupportedCurrency(supportedCurrencies: [.USD]), | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("Unsupported country") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .unsupportedCountry(supportedCountries: [.US, .GB]), | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview { | ||
| POSIneligibleView( | ||
| reason: .unsupportedCurrency, | ||
| onRefresh: {} | ||
| ) | ||
| #Preview("Not a tablet") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .notTablet, | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("Unsupported iOS version") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .unsupportedIOSVersion, | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("WooCommerce plugin not found") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .wooCommercePluginNotFound, | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("Feature flag disabled") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .featureFlagDisabled, | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("Feature switch disabled") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .featureSwitchDisabled, | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("Site settings unavailable") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .siteSettingsNotAvailable, | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("Feature switch sync failure") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .featureSwitchSyncFailure, | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #Preview("Unsupported WooCommerce version") { | ||
| if #available(iOS 17.0, *) { | ||
| POSIneligibleView( | ||
| reason: .unsupportedWooCommerceVersion(minimumVersion: "9.6.0"), | ||
| onRefresh: {} | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the tab show up on the phone? I can't see it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not now and in the near future, but it's an eligibility check that I thought it's better to throw an ineligible error than
fatalErroror no-op. I can separate the ineligible reason enum into two, one for visibility and the other for eligibility so that this case won't have to be handled. Created an issue in WOOMOB-756.