Skip to content

Commit 0e465a6

Browse files
authored
Merge pull request #8663 from woocommerce/feat/8558-paid-domain-selector
Domain selector for paid domains: UI integration
2 parents 16172ce + 452f171 commit 0e465a6

File tree

11 files changed

+378
-149
lines changed

11 files changed

+378
-149
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,15 +380,15 @@ private extension StoreCreationCoordinator {
380380
sellingStatus: StoreCreationSellingStatusAnswer?,
381381
countryCode: SiteAddress.CountryCode?,
382382
planToPurchase: WPComPlanProduct) {
383-
let domainSelector = DomainSelectorHostingController(viewModel: .init(initialSearchTerm: storeName),
384-
onDomainSelection: { [weak self] domain in
383+
let domainSelector = FreeDomainSelectorHostingController(viewModel: .init(initialSearchTerm: storeName, dataProvider: FreeDomainSelectorDataProvider()),
384+
onDomainSelection: { [weak self] domain in
385385
guard let self else { return }
386386
await self.createStoreAndContinueToStoreSummary(from: navigationController,
387387
name: storeName,
388388
category: category,
389389
sellingStatus: sellingStatus,
390390
countryCode: countryCode,
391-
domain: domain,
391+
domain: domain.name,
392392
planToPurchase: planToPurchase)
393393
}, onSupport: { [weak self] in
394394
self?.showSupport(from: navigationController)

WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainRowView.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ struct DomainRowViewModel {
66
let name: String
77
/// Attributed name to be displayed in the row.
88
let attributedName: AttributedString
9+
/// Attributed detail to be displayed in the row.
10+
let attributedDetail: AttributedString?
911
/// Whether the domain is selected.
1012
let isSelected: Bool
1113

12-
init(domainName: String, searchQuery: String, isSelected: Bool) {
14+
init(domainName: String, attributedDetail: AttributedString?, searchQuery: String, isSelected: Bool) {
1315
self.name = domainName
16+
self.attributedDetail = attributedDetail
1417
self.isSelected = isSelected
1518
self.attributedName = {
1619
var attributedName = AttributedString(domainName)
@@ -38,7 +41,12 @@ struct DomainRowView: View {
3841

3942
var body: some View {
4043
HStack {
41-
Text(viewModel.attributedName)
44+
VStack(alignment: .leading, spacing: Layout.spacingBetweenNameAndDetail) {
45+
Text(viewModel.attributedName)
46+
if let attributedDetail = viewModel.attributedDetail {
47+
Text(attributedDetail)
48+
}
49+
}
4250
if viewModel.isSelected {
4351
Spacer()
4452
Image(uiImage: .checkmarkImage)
@@ -52,14 +60,21 @@ struct DomainRowView: View {
5260
private extension DomainRowView {
5361
enum Layout {
5462
static let insets: EdgeInsets = .init(top: 10, leading: 16, bottom: 10, trailing: 16)
63+
static let spacingBetweenNameAndDetail: CGFloat = 4
5564
}
5665
}
5766

5867
struct DomainRowView_Previews: PreviewProvider {
5968
static var previews: some View {
6069
VStack(alignment: .leading) {
61-
DomainRowView(viewModel: .init(domainName: "whitechristmastrees.mywc.mysite", searchQuery: "White Christmas Trees", isSelected: true))
62-
DomainRowView(viewModel: .init(domainName: "whitechristmastrees.mywc.mysite", searchQuery: "White Christmas", isSelected: false))
70+
DomainRowView(viewModel: .init(domainName: "whitechristmastrees.mywc.mysite",
71+
attributedDetail: nil,
72+
searchQuery: "White Christmas Trees",
73+
isSelected: true))
74+
DomainRowView(viewModel: .init(domainName: "whitechristmastrees.mywc.mysite",
75+
attributedDetail: nil,
76+
searchQuery: "White Christmas",
77+
isSelected: false))
6378
}
6479
}
6580
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import Foundation
2+
import Yosemite
3+
4+
/// Provides domain suggestions data of a generic type.
5+
/// The generic type allows different domain suggestion schemas, like free and paid domains.
6+
protocol DomainSelectorDataProvider {
7+
associatedtype DomainSuggestion
8+
9+
/// Loads domain suggestions async from the remote.
10+
/// - Parameter query: Search query for the domain suggestions.
11+
/// - Returns: A list of domain suggestions.
12+
func loadDomainSuggestions(query: String) async throws -> [DomainSuggestion]
13+
}
14+
15+
/// View model for free domain suggestion UI that shows the domain name.
16+
struct FreeDomainSuggestionViewModel: DomainSuggestionViewProperties, Equatable {
17+
let name: String
18+
let attributedDetail: AttributedString? = nil
19+
20+
init(domainSuggestion: FreeDomainSuggestion) {
21+
self.name = domainSuggestion.name
22+
}
23+
}
24+
25+
/// Provides domain suggestions that are free.
26+
final class FreeDomainSelectorDataProvider: DomainSelectorDataProvider {
27+
private let stores: StoresManager
28+
29+
init(stores: StoresManager = ServiceLocator.stores) {
30+
self.stores = stores
31+
}
32+
33+
@MainActor
34+
func loadDomainSuggestions(query: String) async throws -> [FreeDomainSuggestionViewModel] {
35+
try await withCheckedThrowingContinuation { continuation in
36+
stores.dispatch(DomainAction.loadFreeDomainSuggestions(query: query) { result in
37+
continuation.resume(with: result.map { $0
38+
.filter { $0.isFree }
39+
.map { FreeDomainSuggestionViewModel(domainSuggestion: $0) }
40+
})
41+
})
42+
}
43+
}
44+
}
45+
46+
/// View model for paid domain suggestion UI that shows the domain name and attributed price info.
47+
/// The product ID is for creating a cart after a domain is selected.
48+
struct PaidDomainSuggestionViewModel: DomainSuggestionViewProperties, Equatable {
49+
let name: String
50+
let attributedDetail: AttributedString?
51+
let productID: Int64
52+
53+
init(domainSuggestion: PaidDomainSuggestion) {
54+
self.name = domainSuggestion.name
55+
// TODO: 8558 - attributed price info
56+
self.attributedDetail = .init("\(domainSuggestion.saleCost ?? "no sale") / \(domainSuggestion.cost) / \(domainSuggestion.term)")
57+
self.productID = domainSuggestion.productID
58+
}
59+
}
60+
61+
/// Provides domain suggestions that are paid.
62+
final class PaidDomainSelectorDataProvider: DomainSelectorDataProvider {
63+
private let stores: StoresManager
64+
65+
init(stores: StoresManager = ServiceLocator.stores) {
66+
self.stores = stores
67+
}
68+
69+
@MainActor
70+
func loadDomainSuggestions(query: String) async throws -> [PaidDomainSuggestionViewModel] {
71+
try await withCheckedThrowingContinuation { continuation in
72+
stores.dispatch(DomainAction.loadPaidDomainSuggestions(query: query) { result in
73+
continuation.resume(with: result.map { $0.map { PaidDomainSuggestionViewModel(domainSuggestion: $0) } })
74+
})
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)