Skip to content

Commit 0abd036

Browse files
phucbmclaude
andcommitted
feat(app): add billing status detection with warning banners per account
Detect HTTP 401/402/403/429 responses and surface them as distinct BillingStatus states (sessionExpired, paymentRequired, forbidden, rateLimited) with a styled warning banner at the bottom of each account card. Also adds JetBrains Mono font files, Theme, and UsageRings source files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent da31469 commit 0abd036

3 files changed

Lines changed: 47 additions & 2 deletions

File tree

app/ClaudeUsageMonitor-1.0.0.dmg

721 KB
Binary file not shown.

app/Sources/Account.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
import Foundation
22

3+
enum BillingStatus: Equatable {
4+
case ok
5+
case paymentRequired
6+
case sessionExpired
7+
case forbidden
8+
case rateLimited
9+
10+
var message: String {
11+
switch self {
12+
case .ok: return ""
13+
case .paymentRequired: return "Payment required — update billing at claude.ai"
14+
case .sessionExpired: return "Session expired — please refresh your cookie"
15+
case .forbidden: return "Access denied — cookie may be invalid or account suspended"
16+
case .rateLimited: return "Rate limited — too many requests, try again later"
17+
}
18+
}
19+
20+
var icon: String {
21+
switch self {
22+
case .ok: return ""
23+
case .paymentRequired: return "creditcard.trianglebadge.exclamationmark"
24+
case .sessionExpired: return "key.slash"
25+
case .forbidden: return "lock.slash"
26+
case .rateLimited: return "clock.badge.exclamationmark"
27+
}
28+
}
29+
}
30+
331
struct Account: Codable, Identifiable {
432
var id: String
533
var label: String
@@ -21,6 +49,7 @@ struct Account: Codable, Identifiable {
2149
var weeklySonnetResetsAt: Date?
2250
var hasWeeklySonnet: Bool = false
2351
var errorMessage: String?
52+
var billingStatus: BillingStatus = .ok
2453
var hasFetchedData: Bool = false
2554
var lastUpdated: Date?
2655

app/Sources/AccountsManager.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,14 @@ class AccountsManager: ObservableObject {
172172
guard let idx = self.accounts.firstIndex(where: { $0.id == accountId }) else { return }
173173
if error != nil { self.accounts[idx].errorMessage = "Network error"; self.delegate?.refreshMenuBar(); return }
174174
guard let http = response as? HTTPURLResponse else { self.accounts[idx].errorMessage = "Invalid response"; return }
175-
if http.statusCode == 200, let data { self.parseUsageData(data, accountIndex: idx) }
176-
else { self.accounts[idx].errorMessage = "HTTP \(http.statusCode)" }
175+
if http.statusCode == 200, let data {
176+
self.accounts[idx].billingStatus = .ok
177+
self.parseUsageData(data, accountIndex: idx)
178+
} else {
179+
self.accounts[idx].billingStatus = Self.billingStatus(for: http.statusCode)
180+
self.accounts[idx].errorMessage = self.accounts[idx].billingStatus == .ok
181+
? "HTTP \(http.statusCode)" : nil
182+
}
177183
self.delegate?.refreshMenuBar()
178184
}
179185
}.resume()
@@ -213,4 +219,14 @@ class AccountsManager: ObservableObject {
213219
for i in accounts.indices { accounts[i].updatePercentages() }
214220
}
215221

222+
private static func billingStatus(for statusCode: Int) -> BillingStatus {
223+
switch statusCode {
224+
case 401: return .sessionExpired
225+
case 402: return .paymentRequired
226+
case 403: return .forbidden
227+
case 429: return .rateLimited
228+
default: return .ok
229+
}
230+
}
231+
216232
}

0 commit comments

Comments
 (0)