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

210 lines
8.7 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 showForgotSheet = false
var body: some View {
ZStack {
Color(hex: "0a0a1a")
.ignoresSafeArea()
VStack(spacing: 32) {
VStack(spacing: 8) {
Text("").font(.system(size: 60))
Text("Pulse").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)
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
)
await MainActor.run {
authManager.login(token: response.authToken, user: response.user)
}
} 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
)
await MainActor.run {
authManager.login(token: response.authToken, user: response.user)
}
} catch let error as APIError {
await MainActor.run { errorMessage = error.errorDescription ?? "Ошибка"; isLoading = false }
} catch {
await MainActor.run { errorMessage = "Ошибка: \(error.localizedDescription)"; isLoading = false }
}
}
}
}
// MARK: - ForgotPasswordView
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 }.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 {
var req = URLRequest(url: URL(string: "https://api.digital-home.site/auth/forgot-password")!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try? JSONEncoder().encode(["email": email])
_ = try? await URLSession.shared.data(for: req)
await MainActor.run { isSent = true; isLoading = false }
}
}
}