136 lines
5.2 KiB
Swift
136 lines
5.2 KiB
Swift
import SwiftUI
|
|
|
|
struct LoginScreen: View {
|
|
@EnvironmentObject var appState: AppState
|
|
@State private var username = ""
|
|
@State private var password = ""
|
|
@State private var showPassword = false
|
|
@State private var isLoading = false
|
|
@State private var error: String?
|
|
@State private var isDev = false
|
|
|
|
var body: some View {
|
|
GeometryReader { geo in
|
|
ScrollView {
|
|
VStack(spacing: 12) {
|
|
Image(systemName: "sensor.tag.radiowaves.forward.fill")
|
|
.font(.system(size: 60))
|
|
.foregroundColor(.payfritGreen)
|
|
.padding(.top, 40)
|
|
|
|
Text("Payfrit Beacon")
|
|
.font(.system(size: 28, weight: .bold))
|
|
|
|
Text("Sign in to manage beacons")
|
|
.foregroundColor(.secondary)
|
|
|
|
if isDev {
|
|
Text("DEV MODE — password: 123456")
|
|
.font(.caption)
|
|
.foregroundColor(.red)
|
|
.fontWeight(.medium)
|
|
}
|
|
|
|
VStack(spacing: 12) {
|
|
TextField("Email or Phone", text: $username)
|
|
.textFieldStyle(.roundedBorder)
|
|
.textContentType(.emailAddress)
|
|
.autocapitalization(.none)
|
|
.disableAutocorrection(true)
|
|
|
|
ZStack(alignment: .trailing) {
|
|
Group {
|
|
if showPassword {
|
|
TextField("Password", text: $password)
|
|
.textContentType(.password)
|
|
} else {
|
|
SecureField("Password", text: $password)
|
|
.textContentType(.password)
|
|
}
|
|
}
|
|
.textFieldStyle(.roundedBorder)
|
|
.onSubmit { login() }
|
|
|
|
Button {
|
|
showPassword.toggle()
|
|
} label: {
|
|
Image(systemName: showPassword ? "eye.slash.fill" : "eye.fill")
|
|
.foregroundColor(.secondary)
|
|
.font(.subheadline)
|
|
}
|
|
.padding(.trailing, 8)
|
|
}
|
|
}
|
|
.padding(.top, 8)
|
|
|
|
if let error = error {
|
|
HStack {
|
|
Image(systemName: "exclamationmark.circle.fill")
|
|
.foregroundColor(.red)
|
|
Text(error)
|
|
.foregroundColor(.red)
|
|
.font(.callout)
|
|
Spacer()
|
|
}
|
|
.padding(12)
|
|
.background(Color.red.opacity(0.1))
|
|
.cornerRadius(8)
|
|
}
|
|
|
|
Button(action: login) {
|
|
if isLoading {
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
|
.frame(maxWidth: .infinity, minHeight: 44)
|
|
} else {
|
|
Text("Sign In")
|
|
.font(.headline)
|
|
.frame(maxWidth: .infinity, minHeight: 44)
|
|
}
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(.payfritGreen)
|
|
.disabled(isLoading)
|
|
}
|
|
.padding(.horizontal, 24)
|
|
.frame(minHeight: geo.size.height)
|
|
}
|
|
}
|
|
.background(Color(.systemGroupedBackground))
|
|
.task { isDev = await APIService.shared.isDev }
|
|
}
|
|
|
|
private func login() {
|
|
let user = username.trimmingCharacters(in: .whitespaces)
|
|
let pass = password
|
|
guard !user.isEmpty, !pass.isEmpty else {
|
|
error = "Please enter username and password"
|
|
return
|
|
}
|
|
|
|
isLoading = true
|
|
error = nil
|
|
|
|
Task {
|
|
do {
|
|
let response = try await APIService.shared.login(username: user, password: pass)
|
|
let resolvedPhoto = await APIService.shared.resolvePhotoUrl(response.photoUrl)
|
|
await AuthStorage.shared.saveAuth(
|
|
userId: response.userId,
|
|
token: response.token,
|
|
userName: response.userFirstName,
|
|
photoUrl: resolvedPhoto
|
|
)
|
|
appState.setAuth(
|
|
userId: response.userId,
|
|
token: response.token,
|
|
userName: response.userFirstName,
|
|
photoUrl: resolvedPhoto
|
|
)
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
}
|