// ── Login / Register View ───────────────────────────────────── // Authentication form for ChronoMind via platform-service. import SwiftUI struct CMLoginView: View { @ObservedObject var authService = CMAuthService.shared @State private var isRegister = false @State private var name = "" @State private var email = "" @State private var password = "" private var isLoading: Bool { if case .loading = authService.state { return true } return false } private var errorMessage: String? { if case .error(let msg) = authService.state { return msg } return nil } private var isValidEmail: Bool { let p = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" return email.range(of: p, options: .regularExpression) != nil } private var isValidPassword: Bool { password.count >= 8 && password.rangeOfCharacter(from: .uppercaseLetters) != nil && password.rangeOfCharacter(from: .lowercaseLetters) != nil && password.rangeOfCharacter(from: .decimalDigits) != nil } private var isValid: Bool { isValidEmail && isValidPassword && (!isRegister || !name.trimmingCharacters(in: .whitespaces).isEmpty) } var body: some View { ZStack { CMColors.bg.ignoresSafeArea() ScrollView { VStack(spacing: 24) { Spacer(minLength: 60) Image(systemName: "clock.badge.checkmark") .font(.system(size: 48)) .foregroundColor(CMColors.accent) Text("ChronoMind") .font(CMFonts.display(size: 28)) .foregroundColor(CMColors.text) Text(isRegister ? "Create your account" : "Sign in to your account") .font(CMFonts.body(size: 15)) .foregroundColor(CMColors.textSecondary) VStack(spacing: 14) { if isRegister { TextField("Full Name", text: $name) .textContentType(.name) .autocapitalization(.words) .padding(14) .background(CMColors.surface) .cornerRadius(CMRadius.md) .foregroundColor(CMColors.text) } TextField("Email", text: $email) .textContentType(.emailAddress) .keyboardType(.emailAddress) .autocapitalization(.none) .disableAutocorrection(true) .padding(14) .background(CMColors.surface) .cornerRadius(CMRadius.md) .foregroundColor(CMColors.text) SecureField("Password", text: $password) .textContentType(isRegister ? .newPassword : .password) .padding(14) .background(CMColors.surface) .cornerRadius(CMRadius.md) .foregroundColor(CMColors.text) if !password.isEmpty && isRegister && !isValidPassword { Text("Password needs: 8+ chars, uppercase, lowercase, digit") .font(.caption) .foregroundColor(CMColors.warning) } } .padding(.horizontal, 24) if let errorMessage { Text(errorMessage) .font(.caption) .foregroundColor(CMColors.error) .padding(.horizontal, 24) } Button { Task { if isRegister { await authService.register(name: name, email: email, password: password) } else { await authService.login(email: email, password: password) } } } label: { if isLoading { ProgressView() .tint(.white) .frame(maxWidth: .infinity) .frame(height: 48) } else { Text(isRegister ? "Create Account" : "Sign In") .fontWeight(.semibold) .frame(maxWidth: .infinity) .frame(height: 48) } } .buttonStyle(.borderedProminent) .tint(CMColors.accent) .disabled(!isValid || isLoading) .padding(.horizontal, 24) HStack { Text(isRegister ? "Already have an account?" : "Don't have an account?") .font(.caption) .foregroundColor(CMColors.textSecondary) Button(isRegister ? "Sign In" : "Register") { withAnimation { isRegister.toggle() } } .font(.caption) .foregroundColor(CMColors.accent) } Spacer(minLength: 40) } } } .preferredColorScheme(.dark) } }