Files
pulse-mobile/PulseHealth/Views/LoginView.swift

204 lines
8.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
struct LoginView: View {
@EnvironmentObject var authManager: AuthManager
@State private var email = ""
@State private var password = ""
@State private var name = ""
@State private var isLoading = false
@State private var errorMessage = ""
@State private var showPassword = false
@State private var isRegistering = false
@State private var forgotEmail = ""
@State private var showForgotSheet = false
@State private var forgotSent = false
var body: some View {
ZStack {
LinearGradient(colors: [Color(hex: "1a1a2e"), Color(hex: "16213e")],
startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
VStack(spacing: 32) {
VStack(spacing: 8) {
Text("🫀").font(.system(size: 60))
Text("Pulse Health").font(.largeTitle.bold()).foregroundColor(.white)
Text(isRegistering ? "Создать аккаунт" : "Персональный дашборд здоровья")
.font(.subheadline).foregroundColor(.white.opacity(0.6))
}.padding(.top, 60)
VStack(spacing: 16) {
if isRegistering {
TextField("Имя", text: $name)
.autocorrectionDisabled()
.padding()
.background(Color.white.opacity(0.1))
.cornerRadius(12)
.foregroundColor(.white)
}
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.autocorrectionDisabled()
.padding()
.background(Color.white.opacity(0.1))
.cornerRadius(12)
.foregroundColor(.white)
HStack {
Group {
if showPassword {
TextField("Пароль", text: $password)
.autocapitalization(.none)
.autocorrectionDisabled()
} else {
SecureField("Пароль", text: $password)
}
}
.foregroundColor(.white)
Button(action: { showPassword.toggle() }) {
Image(systemName: showPassword ? "eye.slash.fill" : "eye.fill")
.foregroundColor(.white.opacity(0.6))
}
}
.padding()
.background(Color.white.opacity(0.1))
.cornerRadius(12)
if !errorMessage.isEmpty {
Text(errorMessage)
.foregroundColor(.red)
.font(.caption)
.multilineTextAlignment(.center)
}
Button(action: isRegistering ? register : login) {
if isLoading {
ProgressView().tint(.black)
} else {
Text(isRegistering ? "Зарегистрироваться" : "Войти")
.font(.headline).foregroundColor(.black)
}
}
.frame(maxWidth: .infinity)
.padding()
.background(Color(hex: "00d4aa"))
.cornerRadius(12)
.disabled(isLoading || email.isEmpty || password.isEmpty)
// Forgot password (only on login)
if !isRegistering {
Button(action: { showForgotSheet = true }) {
Text("Забыли пароль?")
.font(.footnote)
.foregroundColor(Color(hex: "00d4aa"))
}
}
// Toggle login/register
HStack {
Text(isRegistering ? "Уже есть аккаунт?" : "Нет аккаунта?")
.foregroundColor(.white.opacity(0.6))
.font(.footnote)
Button(action: {
isRegistering.toggle()
errorMessage = ""
}) {
Text(isRegistering ? "Войти" : "Зарегистрироваться")
.font(.footnote.bold())
.foregroundColor(Color(hex: "00d4aa"))
}
}
}
.padding(.horizontal, 24)
Spacer()
}
.sheet(isPresented: $showForgotSheet) {
ForgotPasswordView(isPresented: $showForgotSheet)
}
}
}
func login() {
isLoading = true; errorMessage = ""
Task {
do {
let response = try await APIService.shared.login(email: email.trimmingCharacters(in: .whitespaces), password: password)
let profile = try? await APIService.shared.getProfile(token: response.token)
await MainActor.run {
authManager.login(token: response.token, name: response.user.name, apiKey: profile?.apiKey ?? "")
}
} catch let error as APIError {
await MainActor.run { errorMessage = error.errorDescription ?? "Ошибка"; isLoading = false }
} catch {
await MainActor.run { errorMessage = "Ошибка: \(error.localizedDescription)"; isLoading = false }
}
}
}
func register() {
isLoading = true; errorMessage = ""
Task {
do {
let response = try await APIService.shared.register(email: email.trimmingCharacters(in: .whitespaces), password: password, name: name)
let profile = try? await APIService.shared.getProfile(token: response.token)
await MainActor.run {
authManager.login(token: response.token, name: response.user.name, apiKey: profile?.apiKey ?? "")
}
} catch let error as APIError {
await MainActor.run { errorMessage = error.errorDescription ?? "Ошибка"; isLoading = false }
} catch {
await MainActor.run { errorMessage = "Ошибка: \(error.localizedDescription)"; isLoading = false }
}
}
}
}
struct ForgotPasswordView: View {
@Binding var isPresented: Bool
@State private var email = ""
@State private var isSent = false
@State private var isLoading = false
var body: some View {
ZStack {
Color(hex: "0a0a1a").ignoresSafeArea()
VStack(spacing: 24) {
Text("Сброс пароля").font(.title2.bold()).foregroundColor(.white)
if isSent {
VStack(spacing: 12) {
Text("").font(.system(size: 50))
Text("Письмо отправлено!\nПроверьте \(email)")
.foregroundColor(.white).multilineTextAlignment(.center)
}
Button("Закрыть") { isPresented = false }
.padding().foregroundColor(Color(hex: "00d4aa"))
} else {
Text("Введите email и мы отправим ссылку для сброса пароля")
.foregroundColor(.white.opacity(0.6)).multilineTextAlignment(.center)
TextField("Email", text: $email)
.keyboardType(.emailAddress).autocapitalization(.none)
.padding().background(Color.white.opacity(0.1)).cornerRadius(12).foregroundColor(.white)
Button(action: send) {
if isLoading { ProgressView().tint(.black) }
else { Text("Отправить").font(.headline).foregroundColor(.black) }
}
.frame(maxWidth: .infinity).padding()
.background(Color(hex: "00d4aa")).cornerRadius(12).disabled(email.isEmpty || isLoading)
Button("Отмена") { isPresented = false }.foregroundColor(.white.opacity(0.5))
}
}.padding(32)
}
}
func send() {
isLoading = true
Task {
try? await APIService.shared.forgotPassword(email: email)
await MainActor.run { isSent = true; isLoading = false }
}
}
}