90 lines
2.8 KiB
Swift
90 lines
2.8 KiB
Swift
import SwiftUI
|
|
import AuthenticationServices
|
|
|
|
struct LoginView: View {
|
|
@EnvironmentObject var authManager: AuthManager
|
|
@State private var isLoading = false
|
|
@State private var errorMessage: String?
|
|
|
|
var body: some View {
|
|
VStack(spacing: 24) {
|
|
Spacer()
|
|
|
|
Image(systemName: "timer")
|
|
.font(.system(size: 64))
|
|
.foregroundStyle(Color.accentColor)
|
|
|
|
Text("TimeTracker")
|
|
.font(.largeTitle)
|
|
.fontWeight(.bold)
|
|
|
|
Text("Track your time spent on projects")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
|
|
Spacer()
|
|
|
|
if let error = errorMessage {
|
|
Text(error)
|
|
.font(.subheadline)
|
|
.foregroundStyle(.red)
|
|
.padding(.horizontal)
|
|
}
|
|
|
|
Button {
|
|
login()
|
|
} label: {
|
|
if isLoading {
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
|
.frame(maxWidth: .infinity)
|
|
} else {
|
|
Text("Sign In")
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.controlSize(.large)
|
|
.disabled(isLoading)
|
|
.padding(.horizontal, 40)
|
|
|
|
Spacer()
|
|
.frame(height: 40)
|
|
}
|
|
.padding()
|
|
.onReceive(NotificationCenter.default.publisher(for: .authCallbackReceived)) { notification in
|
|
handleAuthCallback(notification.userInfo)
|
|
}
|
|
.onReceive(NotificationCenter.default.publisher(for: .authError)) { notification in
|
|
if let authError = notification.userInfo?["error"] as? AuthError {
|
|
isLoading = false
|
|
errorMessage = authError.errorDescription
|
|
}
|
|
}
|
|
}
|
|
|
|
private func login() {
|
|
isLoading = true
|
|
errorMessage = nil
|
|
|
|
Task {
|
|
let authService = AuthService.shared
|
|
do {
|
|
try await authService.login(presentationAnchor: nil)
|
|
} catch {
|
|
await MainActor.run {
|
|
isLoading = false
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func handleAuthCallback(_ userInfo: [AnyHashable: Any]?) {
|
|
// AuthManager.handleTokenResponse() already set isAuthenticated = true
|
|
// and populated currentUser during the token exchange in AuthService.
|
|
// No further network call is needed here.
|
|
isLoading = false
|
|
}
|
|
}
|