@@ -13,6 +13,9 @@ struct LicenseSettingsView: View {
1313
1414 @State private var licenseKeyInput = " "
1515 @State private var isActivating = false
16+ @State private var activations : [ LicenseActivationInfo ] = [ ]
17+ @State private var maxActivations = 0
18+ @State private var isLoadingActivations = false
1619
1720 var body : some View {
1821 Form {
@@ -24,12 +27,26 @@ struct LicenseSettingsView: View {
2427 }
2528 . formStyle ( . grouped)
2629 . scrollContentBackground ( . hidden)
30+ . task { await loadActivations ( ) }
2731 }
2832
2933 // MARK: - Licensed State
3034
3135 @ViewBuilder
3236 private func licensedSection( _ license: License ) -> some View {
37+ if licenseManager. isExpiringSoon, let days = licenseManager. daysUntilExpiry {
38+ HStack {
39+ Image ( systemName: " exclamationmark.triangle.fill " )
40+ . foregroundStyle ( . orange)
41+ Text ( " License expires in \( days) day(s) " )
42+ Spacer ( )
43+ Link ( String ( localized: " Renew " ) , destination: URL ( string: " https://tablepro.app/pricing " ) !)
44+ . controlSize ( . small)
45+ }
46+ . padding ( 12 )
47+ . background ( . orange. opacity ( 0.1 ) , in: RoundedRectangle ( cornerRadius: 8 ) )
48+ }
49+
3350 Section ( " License " ) {
3451 LabeledContent ( " Email: " , value: license. email)
3552
@@ -56,7 +73,61 @@ struct LicenseSettingsView: View {
5673 }
5774 }
5875
76+ Section ( " Activations ( \( activations. count) of \( maxActivations) ) " ) {
77+ if isLoadingActivations {
78+ HStack {
79+ Spacer ( )
80+ ProgressView ( )
81+ . controlSize ( . small)
82+ Spacer ( )
83+ }
84+ } else if activations. isEmpty {
85+ Text ( " No activations found " )
86+ . foregroundStyle ( . secondary)
87+ } else {
88+ ForEach ( activations) { activation in
89+ HStack {
90+ VStack ( alignment: . leading, spacing: 2 ) {
91+ HStack {
92+ Text ( activation. machineName)
93+ . fontWeight (
94+ activation. machineId == LicenseStorage . shared. machineId
95+ ? . semibold : . regular
96+ )
97+ if activation. machineId == LicenseStorage . shared. machineId {
98+ Text ( " (this Mac) " )
99+ . font ( . caption)
100+ . foregroundStyle ( . secondary)
101+ }
102+ }
103+ Text ( activation. appVersion + " · " + activation. osVersion)
104+ . font ( . caption)
105+ . foregroundStyle ( . secondary)
106+ }
107+ Spacer ( )
108+ }
109+ }
110+ }
111+
112+ HStack {
113+ Spacer ( )
114+ Button ( " Refresh " ) {
115+ Task { await loadActivations ( ) }
116+ }
117+ . disabled ( isLoadingActivations)
118+ }
119+ }
120+
59121 Section ( " Maintenance " ) {
122+ HStack {
123+ Text ( " Refresh license status from server " )
124+ Spacer ( )
125+ Button ( " Check Status " ) {
126+ Task { await licenseManager. revalidate ( ) }
127+ }
128+ . disabled ( licenseManager. isValidating)
129+ }
130+
60131 HStack {
61132 Text ( " Remove license from this machine " )
62133 Spacer ( )
@@ -103,7 +174,7 @@ struct LicenseSettingsView: View {
103174
104175 HStack {
105176 Spacer ( )
106- Link ( " Purchase License " , destination: URL ( string: " https://tablepro.app " ) !)
177+ Link ( " Purchase License " , destination: URL ( string: " https://tablepro.app/pricing " ) !)
107178 . font ( . subheadline)
108179 }
109180 }
@@ -121,6 +192,23 @@ struct LicenseSettingsView: View {
121192
122193 // MARK: - Actions
123194
195+ private func loadActivations( ) async {
196+ guard let license = licenseManager. license else { return }
197+ isLoadingActivations = true
198+ defer { isLoadingActivations = false }
199+
200+ do {
201+ let response = try await LicenseAPIClient . shared. listActivations (
202+ licenseKey: license. key,
203+ machineId: LicenseStorage . shared. machineId
204+ )
205+ activations = response. activations
206+ maxActivations = response. maxActivations
207+ } catch {
208+ // Silently fail — activations section is informational
209+ }
210+ }
211+
124212 private func activate( ) async {
125213 isActivating = true
126214 defer { isActivating = false }
0 commit comments