Skip to content

Commit 636a273

Browse files
authored
Merge pull request #8126 from woocommerce/feat/8117-store-name-form
Store creation M2: store name form
2 parents 4e85fe2 + 3b1d1a2 commit 636a273

File tree

4 files changed

+192
-13
lines changed

4 files changed

+192
-13
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import SwiftUI
2+
3+
/// Hosting controller that wraps the `StoreNameForm`.
4+
final class StoreNameFormHostingController: UIHostingController<StoreNameForm> {
5+
private let onContinue: (String) -> Void
6+
private let onClose: () -> Void
7+
8+
init(onContinue: @escaping (String) -> Void,
9+
onClose: @escaping () -> Void) {
10+
self.onContinue = onContinue
11+
self.onClose = onClose
12+
super.init(rootView: StoreNameForm())
13+
14+
rootView.onContinue = { [weak self] storeName in
15+
self?.onContinue(storeName)
16+
}
17+
}
18+
19+
@available(*, unavailable)
20+
required dynamic init?(coder aDecoder: NSCoder) {
21+
fatalError("init(coder:) has not been implemented")
22+
}
23+
24+
override func viewDidLoad() {
25+
super.viewDidLoad()
26+
27+
configureNavigationBarAppearance()
28+
}
29+
30+
/// Shows a transparent navigation bar without a bottom border and with a close button to dismiss.
31+
func configureNavigationBarAppearance() {
32+
addCloseNavigationBarButton(title: Localization.cancelButtonTitle,
33+
target: self,
34+
action: #selector(closeButtonTapped))
35+
let appearance = UINavigationBarAppearance()
36+
appearance.configureWithTransparentBackground()
37+
appearance.backgroundColor = .systemBackground
38+
39+
navigationItem.standardAppearance = appearance
40+
navigationItem.scrollEdgeAppearance = appearance
41+
navigationItem.compactAppearance = appearance
42+
}
43+
44+
@objc private func closeButtonTapped() {
45+
onClose()
46+
}
47+
}
48+
49+
private extension StoreNameFormHostingController {
50+
enum Localization {
51+
static let cancelButtonTitle = NSLocalizedString("Cancel", comment: "Navigation bar button on the store name form to leave the store creation flow.")
52+
}
53+
}
54+
55+
/// Allows the user to enter a store name during the store creation flow.
56+
struct StoreNameForm: View {
57+
/// Set in the hosting controller.
58+
var onContinue: (String) -> Void = { _ in }
59+
60+
@State private var name: String = ""
61+
62+
var body: some View {
63+
VStack(alignment: .leading, spacing: 0) {
64+
ScrollView {
65+
VStack(alignment: .leading, spacing: 40) {
66+
VStack(alignment: .leading, spacing: 16) {
67+
// Top header label.
68+
Text(Localization.topHeader)
69+
.foregroundColor(Color(.secondaryLabel))
70+
.footnoteStyle()
71+
72+
// Title label.
73+
Text(Localization.title)
74+
.fontWeight(.bold)
75+
.titleStyle()
76+
77+
// Subtitle label.
78+
Text(Localization.subtitle)
79+
.foregroundColor(Color(.secondaryLabel))
80+
.bodyStyle()
81+
}
82+
83+
VStack(alignment: .leading, spacing: 16) {
84+
// Text field prompt label.
85+
Text(Localization.textFieldPrompt)
86+
.foregroundColor(Color(.label))
87+
.bodyStyle()
88+
89+
// Store name text field.
90+
TextField(Localization.textFieldPlaceholder, text: $name)
91+
.font(.body)
92+
.textFieldStyle(RoundedBorderTextFieldStyle(focused: false))
93+
.focused()
94+
}
95+
}
96+
.padding(Layout.contentPadding)
97+
}
98+
99+
// Continue button.
100+
Button(Localization.continueButtonTitle) {
101+
onContinue(name)
102+
}
103+
.padding(Layout.defaultButtonPadding)
104+
.buttonStyle(PrimaryButtonStyle())
105+
.disabled(name.isEmpty)
106+
}
107+
}
108+
}
109+
110+
private extension StoreNameForm {
111+
enum Layout {
112+
static let spacingBetweenSubtitleAndStoreInfo: CGFloat = 40
113+
static let spacingBetweenStoreNameAndDomain: CGFloat = 4
114+
static let defaultHorizontalPadding: CGFloat = 16
115+
static let dividerHeight: CGFloat = 1
116+
static let contentPadding: EdgeInsets = .init(top: 38, leading: 16, bottom: 16, trailing: 16)
117+
static let defaultButtonPadding: EdgeInsets = .init(top: 16, leading: 16, bottom: 16, trailing: 16)
118+
static let storeInfoPadding: EdgeInsets = .init(top: 16, leading: 16, bottom: 16, trailing: 16)
119+
static let storeInfoCornerRadius: CGFloat = 8
120+
}
121+
122+
enum Localization {
123+
static let topHeader = NSLocalizedString(
124+
"ABOUT YOUR STORE",
125+
comment: "Header label on the top of the store name form in the store creation flow."
126+
)
127+
static let title = NSLocalizedString(
128+
"What’s your store name?",
129+
comment: "Title label on the store name form in the store creation flow."
130+
)
131+
static let subtitle = NSLocalizedString(
132+
"Don’t worry you can always change it later.",
133+
comment: "Subtitle label on the store name form in the store creation flow."
134+
)
135+
static let textFieldPrompt = NSLocalizedString(
136+
"Store name",
137+
comment: "Text field prompt on the store name form in the store creation flow."
138+
)
139+
static let textFieldPlaceholder = NSLocalizedString(
140+
"Type a name for your store",
141+
comment: "Text field placeholder on the store name form in the store creation flow."
142+
)
143+
static let continueButtonTitle = NSLocalizedString(
144+
"Continue",
145+
comment: "Title of the button on the store creation store name form to continue."
146+
)
147+
}
148+
}
149+
150+
struct StoreNameForm_Previews: PreviewProvider {
151+
static var previews: some View {
152+
StoreNameForm()
153+
}
154+
}

WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,14 @@ private extension StoreCreationCoordinator {
7272
let storeCreationNavigationController = UINavigationController()
7373
storeCreationNavigationController.navigationBar.prefersLargeTitles = true
7474

75-
let domainSelector = DomainSelectorHostingController(viewModel: .init(),
76-
onDomainSelection: { [weak self] domain in
77-
guard let self else { return }
78-
// TODO: add a store name screen before the domain selector screen.
79-
await self.createStoreAndContinueToStoreSummary(from: storeCreationNavigationController, name: "Test store", domain: domain)
80-
}, onSkip: {
81-
// TODO-8045: skip to the next step of store creation with an auto-generated domain.
82-
})
83-
storeCreationNavigationController.pushViewController(domainSelector, animated: false)
75+
let storeNameForm = StoreNameFormHostingController { [weak self] storeName in
76+
self?.showDomainSelector(from: storeCreationNavigationController,
77+
storeName: storeName)
78+
} onClose: { [weak self] in
79+
self?.showDiscardChangesAlert()
80+
}
81+
storeCreationNavigationController.pushViewController(storeNameForm, animated: false)
82+
8483
presentStoreCreation(viewController: storeCreationNavigationController)
8584
}
8685

@@ -192,6 +191,20 @@ private extension StoreCreationCoordinator {
192191
// MARK: - Store creation M2
193192

194193
private extension StoreCreationCoordinator {
194+
func showDomainSelector(from navigationController: UINavigationController,
195+
storeName: String) {
196+
let domainSelector = DomainSelectorHostingController(viewModel: .init(initialSearchTerm: storeName),
197+
onDomainSelection: { [weak self] domain in
198+
guard let self else { return }
199+
await self.createStoreAndContinueToStoreSummary(from: navigationController,
200+
name: storeName,
201+
domain: domain)
202+
}, onSkip: {
203+
// TODO-8045: skip to the next step of store creation with an auto-generated domain.
204+
})
205+
navigationController.pushViewController(domainSelector, animated: false)
206+
}
207+
195208
@MainActor
196209
func createStoreAndContinueToStoreSummary(from navigationController: UINavigationController, name: String, domain: String) async {
197210
let result = await createStore(name: name, domain: domain)

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
020886572499E643001D784E /* ProductExternalLinkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020886562499E642001D784E /* ProductExternalLinkViewController.swift */; };
3232
020A55F127F6C605007843F0 /* CardReaderConnectionAnalyticsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020A55F027F6C605007843F0 /* CardReaderConnectionAnalyticsTracker.swift */; };
3333
020AF66329235860007760E5 /* StoreCreationPlanViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020AF66229235860007760E5 /* StoreCreationPlanViewModel.swift */; };
34+
020AF6662923C7ED007760E5 /* StoreNameForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020AF6652923C7ED007760E5 /* StoreNameForm.swift */; };
3435
020B2F8F23BD9F1F00BD79AD /* IntegerInputFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020B2F8E23BD9F1F00BD79AD /* IntegerInputFormatter.swift */; };
3536
020B2F9123BDD71500BD79AD /* IntegerInputFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020B2F9023BDD71500BD79AD /* IntegerInputFormatterTests.swift */; };
3637
020B2F9423BDDBDC00BD79AD /* ProductUpdateError+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020B2F9323BDDBDC00BD79AD /* ProductUpdateError+UI.swift */; };
@@ -1997,6 +1998,7 @@
19971998
020886562499E642001D784E /* ProductExternalLinkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductExternalLinkViewController.swift; sourceTree = "<group>"; };
19981999
020A55F027F6C605007843F0 /* CardReaderConnectionAnalyticsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderConnectionAnalyticsTracker.swift; sourceTree = "<group>"; };
19992000
020AF66229235860007760E5 /* StoreCreationPlanViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreCreationPlanViewModel.swift; sourceTree = "<group>"; };
2001+
020AF6652923C7ED007760E5 /* StoreNameForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreNameForm.swift; sourceTree = "<group>"; };
20002002
020B2F8E23BD9F1F00BD79AD /* IntegerInputFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerInputFormatter.swift; sourceTree = "<group>"; };
20012003
020B2F9023BDD71500BD79AD /* IntegerInputFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerInputFormatterTests.swift; sourceTree = "<group>"; };
20022004
020B2F9323BDDBDC00BD79AD /* ProductUpdateError+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductUpdateError+UI.swift"; sourceTree = "<group>"; };
@@ -3999,6 +4001,14 @@
39994001
path = "Edit External Link";
40004002
sourceTree = "<group>";
40014003
};
4004+
020AF6642923C745007760E5 /* Store name */ = {
4005+
isa = PBXGroup;
4006+
children = (
4007+
020AF6652923C7ED007760E5 /* StoreNameForm.swift */,
4008+
);
4009+
path = "Store name";
4010+
sourceTree = "<group>";
4011+
};
40024012
020B2F9223BDDBC300BD79AD /* Error Handling */ = {
40034013
isa = PBXGroup;
40044014
children = (
@@ -4537,6 +4547,7 @@
45374547
02759B8F28FFA06F00918176 /* Store Creation */ = {
45384548
isa = PBXGroup;
45394549
children = (
4550+
020AF6642923C745007760E5 /* Store name */,
45404551
02EEA92929233F0F00D05F47 /* Plan */,
45414552
02759B9028FFA09600918176 /* StoreCreationWebViewModel.swift */,
45424553
02E3B63029066858007E0F13 /* StoreCreationCoordinator.swift */,
@@ -10317,6 +10328,7 @@
1031710328
D831E2E0230E0BA7000037D0 /* Logs.swift in Sources */,
1031810329
02CEBB8224C98861002EDF35 /* ProductFormDataModel.swift in Sources */,
1031910330
3120491B26DD80E000A4EC4F /* ActivitySpinnerAndLabelTableViewCell.swift in Sources */,
10331+
020AF6662923C7ED007760E5 /* StoreNameForm.swift in Sources */,
1032010332
DEC51AFD276AEAE3009F3DF4 /* SystemStatusReportView.swift in Sources */,
1032110333
CECC759C23D61C1400486676 /* AggregateDataHelper.swift in Sources */,
1032210334
02645D7D27BA027B0065DC68 /* Inbox.swift in Sources */,

WooCommerce/WooCommerceTests/Authentication/StoreCreationCoordinatorTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ final class StoreCreationCoordinatorTests: XCTestCase {
7171

7272
// MARK: - Presentation in different states for store creation M2
7373

74-
func test_DomainSelectorHostingController_is_presented_when_navigationController_is_presenting_another_view() throws {
74+
func test_StoreNameFormHostingController_is_presented_when_navigationController_is_presenting_another_view() throws {
7575
// Given
7676
let featureFlagService = MockFeatureFlagService(isStoreCreationM2Enabled: true)
7777
let coordinator = StoreCreationCoordinator(source: .storePicker,
@@ -92,10 +92,10 @@ final class StoreCreationCoordinatorTests: XCTestCase {
9292
self.navigationController.presentedViewController is UINavigationController
9393
}
9494
let storeCreationNavigationController = try XCTUnwrap(navigationController.presentedViewController as? UINavigationController)
95-
assertThat(storeCreationNavigationController.topViewController, isAnInstanceOf: DomainSelectorHostingController.self)
95+
assertThat(storeCreationNavigationController.topViewController, isAnInstanceOf: StoreNameFormHostingController.self)
9696
}
9797

98-
func test_AuthenticatedWebViewController_is_presented_when_navigationController_is_showing_another_view() throws {
98+
func test_StoreNameFormHostingController_is_presented_when_navigationController_is_showing_another_view() throws {
9999
// Given
100100
let featureFlagService = MockFeatureFlagService(isStoreCreationM2Enabled: true)
101101
navigationController.show(.init(), sender: nil)
@@ -113,6 +113,6 @@ final class StoreCreationCoordinatorTests: XCTestCase {
113113
self.navigationController.presentedViewController is UINavigationController
114114
}
115115
let storeCreationNavigationController = try XCTUnwrap(navigationController.presentedViewController as? UINavigationController)
116-
assertThat(storeCreationNavigationController.topViewController, isAnInstanceOf: DomainSelectorHostingController.self)
116+
assertThat(storeCreationNavigationController.topViewController, isAnInstanceOf: StoreNameFormHostingController.self)
117117
}
118118
}

0 commit comments

Comments
 (0)