1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
import StoreKit
import SwiftUI
struct PaywallView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.appDensity) private var appDensity
@State private var purchaseService = PurchaseService.shared
var body: some View {
NavigationStack {
List {
Section {
Text("Pro unlocks automation, larger runs, and export convenience while keeping the core lookup experience free.")
.font(appDensity.font(.body, design: .default))
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
Section("What Pro Unlocks") {
featureRow("Unlimited tracked domains")
featureRow("Workflows")
featureRow("Larger batch sizes")
featureRow("Advanced exports")
}
Section("Subscription") {
if purchaseService.isLoadingProducts {
ProgressView("Loading pricing…")
} else if purchaseService.products.isEmpty {
Text("Pricing is unavailable right now.")
.font(appDensity.font(.caption, design: .default))
.foregroundStyle(.secondary)
Button("Retry") {
Task {
await purchaseService.refreshProducts()
}
}
} else {
ForEach(purchaseService.products, id: \.id) { product in
Button {
Task {
await purchaseService.purchase(product)
}
} label: {
VStack(alignment: .leading, spacing: 6) {
Text(subscriptionTitle(for: product))
.font(appDensity.font(.headline, design: .default, weight: .semibold))
.foregroundStyle(.primary)
Text(product.displayPrice)
.font(appDensity.font(.callout, design: .default))
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.disabled(purchaseService.isPurchasing || purchaseService.isRestoring)
}
}
if let statusMessage = purchaseService.statusMessage {
Text(statusMessage)
.font(appDensity.font(.caption, design: .default))
.foregroundStyle(.secondary)
}
if let errorMessage = purchaseService.errorMessage {
Text(errorMessage)
.font(appDensity.font(.caption, design: .default))
.foregroundStyle(.red)
}
}
Section("Account") {
Button(purchaseService.isRestoring ? "Restoring…" : "Restore Purchases") {
Task {
await purchaseService.restorePurchases()
}
}
.disabled(purchaseService.isPurchasing || purchaseService.isRestoring)
if purchaseService.hasProAccess {
Button("Manage Subscription") {
Task {
await purchaseService.manageSubscription()
}
}
}
}
}
.navigationTitle("DomainDig Pro")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
dismiss()
}
}
}
}
.task {
await purchaseService.refreshProducts()
await purchaseService.refreshEntitlements()
}
.onDisappear {
purchaseService.clearMessages()
}
}
private func featureRow(_ title: String) -> some View {
Text(title)
.font(appDensity.font(.body, design: .default))
}
private func subscriptionTitle(for product: Product) -> String {
switch product.id {
case PurchaseService.monthlyProductID:
return "Pro Monthly"
case PurchaseService.yearlyProductID:
return "Pro Yearly"
default:
return product.displayName
}
}
}
|