bugger/Sources/Views/OAuth/OAuthSetupView.swift

95 lines
3.3 KiB
Swift

import AppKit
import SwiftUI
struct OAuthSetupView: View {
@State private var tokenManager = TokenManager.shared
@State private var statusMessage: String?
var body: some View {
VStack(spacing: 16) {
Image(systemName: "link.circle")
.font(.system(size: 40))
.foregroundStyle(.blue)
Text("Connect Feishu")
.font(.headline)
Text("Authorize Bugger to read bugs assigned to you from your Feishu Bitable.")
.font(.caption)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
if case .authenticating = tokenManager.state {
ProgressView("Completing sign-in...")
} else if tokenManager.authorizeURL == nil {
Text("Set FEISHU_APP_ID and FEISHU_APP_SECRET in the Xcode build settings before connecting.")
.font(.caption)
.foregroundStyle(.orange)
.multilineTextAlignment(.center)
} else {
Button("Connect Feishu") {
connectFeishu()
}
.buttonStyle(.borderedProminent)
.disabled(statusMessage != nil)
}
if case .error(let message) = tokenManager.state {
VStack(spacing: 4) {
Text("Authorization failed")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.red)
Text(message)
.font(.caption2)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Button("Try again") {
tokenManager.clearTokens()
statusMessage = nil
}
.font(.caption)
}
}
if let statusMessage, !statusMessage.isEmpty,
tokenManager.state != .authenticating {
Text(statusMessage)
.font(.caption)
.foregroundStyle(.secondary)
}
}
.padding(24)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private func connectFeishu() {
statusMessage = "Starting..."
Task {
let server = LocalOAuthServer(port: 18_923)
tokenManager.authService.redirectURI = server.redirectURI
guard let url = tokenManager.authorizeURL else {
statusMessage = nil
tokenManager.setError("Missing credentials. Check FEISHU_APP_ID.")
return
}
statusMessage = "Complete authorization in your browser..."
NSWorkspace.shared.open(url)
do {
let code = try await server.receiveCode()
statusMessage = "Completing sign-in..."
try await tokenManager.handleCallback(code: code)
statusMessage = nil
await PollerService.shared.startIfConfigured()
await PollerService.shared.fetchNow()
} catch {
statusMessage = nil
tokenManager.setError(error.localizedDescription)
}
}
}
}