Skip to content

Commit d00bd60

Browse files
authored
Merge pull request #7484 from woocommerce/issue/7468-no-stores-site-discovery
Login: Site discovery on the empty store picker
2 parents 1bf66d3 + 557e235 commit d00bd60

File tree

12 files changed

+337
-72
lines changed

12 files changed

+337
-72
lines changed

Podfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ target 'WooCommerce' do
4242
pod 'Gridicons', '~> 1.2.0'
4343

4444
# To allow pod to pick up beta versions use -beta. E.g., 1.1.7-beta.1
45-
pod 'WordPressAuthenticator', '~> 2.2.1-beta.2'
46-
# pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :commit => ''
45+
pod 'WordPressAuthenticator', '~> 2.2.1-beta.3'
46+
# pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :commit => '7cdd0c6e44fb1f5356a974cdd6a05c3d2d5210f6'
4747
# pod 'WordPressAuthenticator', :git => 'https://github.com/wordpress-mobile/WordPressAuthenticator-iOS.git', :branch => ''
4848
# pod 'WordPressAuthenticator', :path => '../WordPressAuthenticator-iOS'
4949

Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ PODS:
4242
- WordPress-Aztec-iOS (1.11.0)
4343
- WordPress-Editor-iOS (1.11.0):
4444
- WordPress-Aztec-iOS (= 1.11.0)
45-
- WordPressAuthenticator (2.2.1-beta.2):
45+
- WordPressAuthenticator (2.2.1-beta.3):
4646
- Alamofire (~> 4.8)
4747
- CocoaLumberjack (~> 3.5)
4848
- GoogleSignIn (~> 6.0.1)
@@ -92,7 +92,7 @@ DEPENDENCIES:
9292
- Sourcery (~> 1.0.3)
9393
- StripeTerminal (~> 2.7)
9494
- WordPress-Editor-iOS (~> 1.11.0)
95-
- WordPressAuthenticator (~> 2.2.1-beta.2)
95+
- WordPressAuthenticator (~> 2.2.1-beta.3)
9696
- WordPressKit (~> 4.49.0)
9797
- WordPressShared (~> 1.15)
9898
- WordPressUI (~> 1.12.5)
@@ -163,7 +163,7 @@ SPEC CHECKSUMS:
163163
UIDeviceIdentifier: af4e11e25a2ea670078e2bd677bb0e8144f9f063
164164
WordPress-Aztec-iOS: 050b34d4c3adfb7c60363849049b13d60683b348
165165
WordPress-Editor-iOS: 304098424f1051cb271546c99f906aac296b1b81
166-
WordPressAuthenticator: 5695f2f517df46414456195914eccfe2a6be4b1b
166+
WordPressAuthenticator: 40d028b26ceeb093620d3748198f142f4e416e65
167167
WordPressKit: 96deb6ba37ea5eaec4ddcaa53eca04d653246152
168168
WordPressShared: 5477f179c7fe03b5d574f91adda66f67d131827e
169169
WordPressUI: c5be816f6c7b3392224ac21de9e521e89fa108ac
@@ -179,6 +179,6 @@ SPEC CHECKSUMS:
179179
ZendeskSupportProvidersSDK: 2bdf8544f7cd0fd4c002546f5704b813845beb2a
180180
ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba
181181

182-
PODFILE CHECKSUM: a9489557183401ff27ecf7a8b78378f0e3363892
182+
PODFILE CHECKSUM: 2f80965199751d9b642db8d680aa08c11547a548
183183

184184
COCOAPODS: 1.11.2

RELEASE-NOTES.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
10.0
44
-----
5-
65
- [**] In-Person Payments and Simple Payments have been moved to a new Payments section [https://github.com/woocommerce/woocommerce-ios/pull/7473]
76
- [*] Login: on the WP.com password screen, the magic link login option is moved from below "Reset your password" to below the primary Continue button for higher visibility. [https://github.com/woocommerce/woocommerce-ios/pull/7469]
87
- [*] Login: some minor enhancements are made to the error screen after entering an invalid WP.com email - a new "What is WordPress.com?" link, hiding the "Log in with store address" button when it's from the store address login flow, and some copy changes. [https://github.com/woocommerce/woocommerce-ios/pull/7485]
98
- [**] In-Person Payments: Accounts with pending requirements are no longer blocked from taking payments - we have added a skip button to the relevant screen. [https://github.com/woocommerce/woocommerce-ios/pull/7504]
9+
- [*] Login: New button added to the empty site picker screen to enter a site address for troubleshooting. [https://github.com/woocommerce/woocommerce-ios/pull/7484]
1010

1111
9.9
1212
-----

WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,3 +1398,34 @@ extension WooAnalyticsEvent {
13981398
}
13991399
}
14001400
}
1401+
1402+
// MARK: - Site picker
1403+
//
1404+
extension WooAnalyticsEvent {
1405+
enum SitePicker {
1406+
1407+
enum Key: String {
1408+
case hasWordPress = "has_wordpress"
1409+
case isWPCom = "is_wpcom"
1410+
case hasValidJetpack = "has_valid_jetpack"
1411+
}
1412+
1413+
/// Tracks when the user taps the Enter Your Store Address button
1414+
static func enterStoreAddressTapped() -> WooAnalyticsEvent {
1415+
WooAnalyticsEvent(statName: .sitePickerEnterStoreAddressTapped, properties: [:])
1416+
}
1417+
1418+
/// Tracks when the result for site discovery is returned
1419+
static func siteDiscovery(hasWordPress: Bool, isWPCom: Bool, hasValidJetpack: Bool) -> WooAnalyticsEvent {
1420+
WooAnalyticsEvent(statName: .sitePickerSiteDiscovery, properties: [Key.hasWordPress.rawValue: hasWordPress,
1421+
Key.isWPCom.rawValue: isWPCom,
1422+
Key.hasValidJetpack.rawValue: hasValidJetpack])
1423+
}
1424+
1425+
/// Tracks when the user taps the New To WooCommerce button
1426+
///
1427+
static func newToWooTapped() -> WooAnalyticsEvent {
1428+
WooAnalyticsEvent(statName: .sitePickerNewToWooTapped, properties: [:])
1429+
}
1430+
}
1431+
}

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ public enum WooAnalyticsStat: String {
115115
case sitePickerStoresShown = "site_picker_stores_shown"
116116
case sitePickerHelpButtonTapped = "site_picker_help_button_tapped"
117117
case sitePickerNonWooSiteTapped = "site_picker_non_woo_site_tapped"
118+
case sitePickerEnterStoreAddressTapped = "site_picker_enter_store_address_tapped"
119+
case sitePickerSiteDiscovery = "site_picker_site_discovery"
120+
case sitePickerNewToWooTapped = "site_picker_new_to_woo_tapped"
118121

119122
// MARK: Help & Support Events
120123
//

WooCommerce/Classes/Authentication/AuthenticationManager.swift

Lines changed: 111 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -192,23 +192,16 @@ class AuthenticationManager: Authentication {
192192
/// Account mismatched case
193193
guard matcher.match(originalURL: siteURL) else {
194194
DDLogWarn("⚠️ Present account mismatch error for site: \(String(describing: siteURL))")
195-
let viewModel = WrongAccountErrorViewModel(siteURL: siteURL, showsConnectedStores: matcher.hasConnectedStores)
196-
let mismatchAccountUI = ULAccountMismatchViewController(viewModel: viewModel)
197-
return mismatchAccountUI
195+
return accountMismatchUI(for: siteURL, with: matcher)
198196
}
199197

200198
/// No Woo found
201199
if let matchedSite = matcher.matchedSite(originalURL: siteURL),
202200
matchedSite.isWooCommerceActive == false {
203-
let viewModel = NoWooErrorViewModel(
204-
site: matchedSite,
205-
showsConnectedStores: matcher.hasConnectedStores,
206-
onSetupCompletion: { [weak self] siteID in
207-
guard let self = self else { return }
208-
self.startStorePicker(with: siteID, in: navigationController, onDismiss: onStorePickerDismiss)
209-
})
210-
let noWooUI = ULErrorViewController(viewModel: viewModel)
211-
return noWooUI
201+
return noWooUI(for: matchedSite,
202+
with: matcher,
203+
navigationController: navigationController,
204+
onStorePickerDismiss: onStorePickerDismiss)
212205
}
213206

214207
// All good!
@@ -289,10 +282,7 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {
289282

290283
/// WordPress must be present.
291284
guard let site = siteInfo, site.isWP else {
292-
let viewModel = NotWPErrorViewModel()
293-
let notWPErrorUI = ULErrorViewController(viewModel: viewModel)
294-
295-
let authenticationResult: WordPressAuthenticatorResult = .injectViewController(value: notWPErrorUI)
285+
let authenticationResult: WordPressAuthenticatorResult = .injectViewController(value: noWPUI)
296286

297287
onCompletion(authenticationResult)
298288

@@ -319,6 +309,23 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {
319309
onCompletion(authenticationResult)
320310
}
321311

312+
/// Displays appropriate error based on the input `siteInfo`.
313+
/// Data flow following ZwYqDGHdenvYZoPHXZ1SOf-fi
314+
///
315+
func troubleshootSite(_ siteInfo: WordPressComSiteInfo?, in navigationController: UINavigationController?) {
316+
ServiceLocator.analytics.track(event: .SitePicker.siteDiscovery(hasWordPress: siteInfo?.isWP ?? false,
317+
isWPCom: siteInfo?.isWPCom ?? false,
318+
hasValidJetpack: siteInfo?.hasValidJetpack ?? false))
319+
320+
guard let site = siteInfo, let navigationController = navigationController else {
321+
navigationController?.show(noWPUI, sender: nil)
322+
return
323+
}
324+
325+
let errorUI = errorUI(for: site, in: navigationController)
326+
navigationController.show(errorUI, sender: nil)
327+
}
328+
322329
/// Presents the Login Epilogue, in the specified NavigationController.
323330
///
324331
func presentLoginEpilogue(in navigationController: UINavigationController, for credentials: AuthenticatorCredentials, onDismiss: @escaping () -> Void) {
@@ -507,6 +514,8 @@ private extension AuthenticationManager {
507514
return false
508515
}
509516

517+
/// Presents an error if the user tries to log in to a site without Jetpack.
518+
///
510519
func presentJetpackError(for siteURL: String,
511520
with credentials: AuthenticatorCredentials,
512521
in navigationController: UINavigationController,
@@ -542,6 +551,92 @@ private extension AuthenticationManager {
542551
storePickerCoordinator?.start()
543552
}
544553
}
554+
555+
/// The error screen to be displayed when the user enters a site
556+
/// without Jetpack in the site discovery flow
557+
///
558+
func jetpackErrorUI(for siteURL: String, with matcher: ULAccountMatcher, in navigationController: UINavigationController) -> UIViewController {
559+
let viewModel = JetpackErrorViewModel(siteURL: siteURL, onJetpackSetupCompletion: { [weak self] authorizedEmailAddress in
560+
guard let self = self else { return }
561+
562+
// Tries re-syncing to get an updated store list
563+
ServiceLocator.stores.synchronizeEntities { [weak self] in
564+
guard let self = self else { return }
565+
matcher.refreshStoredSites()
566+
guard let matchedSite = matcher.matchedSite(originalURL: siteURL) else {
567+
DDLogWarn("⚠️ Could not find \(siteURL) connected to the account")
568+
return
569+
}
570+
// checks if the site has woo
571+
if matchedSite.isWooCommerceActive == false {
572+
let noWooUI = self.noWooUI(for: matchedSite,
573+
with: matcher,
574+
navigationController: navigationController,
575+
onStorePickerDismiss: {})
576+
navigationController.show(noWooUI, sender: nil)
577+
} else {
578+
self.startStorePicker(with: matchedSite.siteID, in: navigationController, onDismiss: {})
579+
}
580+
}
581+
})
582+
return ULErrorViewController(viewModel: viewModel)
583+
}
584+
585+
/// The error screen to be displayed when the user tries to enter a site
586+
/// whose Jetpack is not associated with their account.
587+
///
588+
func accountMismatchUI(for siteURL: String, with matcher: ULAccountMatcher) -> UIViewController {
589+
let viewModel = WrongAccountErrorViewModel(siteURL: siteURL, showsConnectedStores: matcher.hasConnectedStores)
590+
let mismatchAccountUI = ULAccountMismatchViewController(viewModel: viewModel)
591+
return mismatchAccountUI
592+
}
593+
594+
/// The error screen to be displayed when the user tries to enter a site without WooCommerce.
595+
///
596+
func noWooUI(for site: Site,
597+
with matcher: ULAccountMatcher,
598+
navigationController: UINavigationController,
599+
onStorePickerDismiss: @escaping () -> Void) -> UIViewController {
600+
let viewModel = NoWooErrorViewModel(
601+
site: site,
602+
showsConnectedStores: matcher.hasConnectedStores,
603+
onSetupCompletion: { [weak self] siteID in
604+
guard let self = self else { return }
605+
self.startStorePicker(with: siteID, in: navigationController, onDismiss: onStorePickerDismiss)
606+
})
607+
let noWooUI = ULErrorViewController(viewModel: viewModel)
608+
return noWooUI
609+
}
610+
611+
/// The error screen to be displayed when the user tries to enter a site without WordPress.
612+
///
613+
var noWPUI: UIViewController {
614+
let viewModel = NotWPErrorViewModel()
615+
return ULErrorViewController(viewModel: viewModel)
616+
}
617+
618+
/// Appropriate error to display for a site when entered from the site discovery flow.
619+
///
620+
func errorUI(for site: WordPressComSiteInfo, in navigationController: UINavigationController) -> UIViewController {
621+
guard site.isWP else {
622+
return noWPUI
623+
}
624+
625+
let matcher = ULAccountMatcher(storageManager: storageManager)
626+
matcher.refreshStoredSites()
627+
628+
guard !site.isWPCom else {
629+
// The site doesn't belong to the current account since it was not included in the site picker.
630+
return accountMismatchUI(for: site.url, with: matcher)
631+
}
632+
633+
/// Jetpack is required. Present an error if we don't detect a valid installation.
634+
guard site.hasValidJetpack == true else {
635+
return jetpackErrorUI(for: site.url, with: matcher, in: navigationController)
636+
}
637+
638+
return accountMismatchUI(for: site.url, with: matcher)
639+
}
545640
}
546641

547642
// MARK: - ViewModel Factory

WooCommerce/Classes/Authentication/Epilogue/EmptyStoresTableViewCell.swift

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import UIKit
66
///
77
final class EmptyStoresTableViewCell: UITableViewCell {
88

9-
var onJetpackSetupButtonTapped: (() -> Void)?
10-
119
var onCloseAccountButtonTapped: (() -> Void)?
1210

1311
/// LegendLabel: To be displayed below the ImageView.
@@ -22,7 +20,6 @@ final class EmptyStoresTableViewCell: UITableViewCell {
2220

2321
@IBOutlet private weak var stackView: UIStackView!
2422
@IBOutlet private weak var emptyStoresImageView: UIImageView!
25-
@IBOutlet private weak var actionButton: UIButton!
2623
@IBOutlet private weak var removeAppleIDAccessButton: UIButton!
2724

2825
override func awakeFromNib() {
@@ -31,7 +28,6 @@ final class EmptyStoresTableViewCell: UITableViewCell {
3128
configureBackground()
3229
configureStackView()
3330
configureImageView()
34-
configureActionButton()
3531
configureRemoveAppleIDAccessButton()
3632
updateRemoveAppleIDAccessButtonVisibility(isVisible: false)
3733
}
@@ -56,14 +52,6 @@ private extension EmptyStoresTableViewCell {
5652
emptyStoresImageView.contentMode = .scaleAspectFit
5753
}
5854

59-
func configureActionButton() {
60-
actionButton.applyPrimaryButtonStyle()
61-
actionButton.setTitle(Localization.actionTitle, for: .normal)
62-
actionButton.on(.touchUpInside) { [weak self] _ in
63-
self?.onJetpackSetupButtonTapped?()
64-
}
65-
}
66-
6755
func configureRemoveAppleIDAccessButton() {
6856
removeAppleIDAccessButton.applyLinkButtonStyle()
6957
removeAppleIDAccessButton.setTitle(Localization.closeAccountTitle, for: .normal)
@@ -75,14 +63,12 @@ private extension EmptyStoresTableViewCell {
7563

7664
private extension EmptyStoresTableViewCell {
7765
enum Localization {
78-
static let actionTitle = NSLocalizedString("Connect your store with Jetpack",
79-
comment: "Link on the store picker when there are no stores available. Opens a webview about Jetpack setup.")
8066
static let closeAccountTitle = NSLocalizedString(
8167
"Close Account",
8268
comment: "Link on the store picker for users who signed in with Apple to close their WordPress.com account."
8369
)
8470
static let legend =
85-
NSLocalizedString("If you already have a store, you’ll need to install the free Jetpack plugin and connect it to your WordPress.com account.",
71+
NSLocalizedString("We couldn't find a WooCommerce store connected to your account.",
8672
comment: "Displayed during the Login flow, whenever the user has no woo stores associated.")
8773
}
8874
}

WooCommerce/Classes/Authentication/Epilogue/EmptyStoresTableViewCell.xib

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21179.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
33
<device id="retina4_7" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21169.4"/>
77
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
88
</dependencies>
99
<objects>
@@ -20,20 +20,16 @@
2020
<rect key="frame" x="0.0" y="0.0" width="375" height="222"/>
2121
<subviews>
2222
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="woo-no-store" translatesAutoresizingMaskIntoConstraints="NO" id="uuh-xp-DA5">
23-
<rect key="frame" x="0.0" y="0.0" width="375" height="140.5"/>
23+
<rect key="frame" x="0.0" y="0.0" width="375" height="152"/>
2424
</imageView>
2525
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bPV-bJ-GBz">
26-
<rect key="frame" x="0.0" y="140.5" width="375" height="20.5"/>
26+
<rect key="frame" x="0.0" y="152" width="375" height="20.5"/>
2727
<fontDescription key="fontDescription" type="system" pointSize="17"/>
2828
<nil key="textColor"/>
2929
<nil key="highlightedColor"/>
3030
</label>
31-
<button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vE6-Wu-Y7b">
32-
<rect key="frame" x="0.0" y="161" width="375" height="30"/>
33-
<state key="normal" title="Button"/>
34-
</button>
3531
<button opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XF9-r3-Vq9" userLabel="Remove Apple ID Access Button">
36-
<rect key="frame" x="0.0" y="191" width="375" height="31"/>
32+
<rect key="frame" x="0.0" y="172.5" width="375" height="49.5"/>
3733
<state key="normal" title="Button"/>
3834
<buttonConfiguration key="configuration" style="plain" title="Button"/>
3935
</button>
@@ -50,7 +46,6 @@
5046
</tableViewCellContentView>
5147
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
5248
<connections>
53-
<outlet property="actionButton" destination="vE6-Wu-Y7b" id="ecz-oF-tox"/>
5449
<outlet property="emptyStoresImageView" destination="uuh-xp-DA5" id="4yf-Rz-3c2"/>
5550
<outlet property="legendLabel" destination="bPV-bJ-GBz" id="sQX-ir-ghQ"/>
5651
<outlet property="removeAppleIDAccessButton" destination="XF9-r3-Vq9" id="sLP-Jl-0hK"/>

0 commit comments

Comments
 (0)