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 } } } }