import SwiftUI struct AuthView: View { @EnvironmentObject var authStore: AuthStore @State private var mode: Mode = .login @State private var username: String = "" @State private var password: String = "" @State private var confirmPassword: String = "" @FocusState private var focusedField: Field? enum Mode { case login, register } enum Field { case username, password, confirmPassword } var body: some View { NavigationStack { VStack(spacing: 0) { // Logo / header VStack(spacing: 8) { Image(systemName: "books.vertical.fill") .font(.system(size: 56)) .foregroundStyle(.amber) Text("LibNovel") .font(.largeTitle.bold()) } .padding(.top, 60) .padding(.bottom, 40) // Tab switcher Picker("Mode", selection: $mode) { Text("Sign In").tag(Mode.login) Text("Create Account").tag(Mode.register) } .pickerStyle(.segmented) .padding(.horizontal, 24) .padding(.bottom, 32) // Form VStack(spacing: 16) { TextField("Username", text: $username) .textFieldStyle(.roundedBorder) .textInputAutocapitalization(.never) .autocorrectionDisabled() .focused($focusedField, equals: .username) .submitLabel(.next) .onSubmit { focusedField = .password } SecureField("Password", text: $password) .textFieldStyle(.roundedBorder) .focused($focusedField, equals: .password) .submitLabel(mode == .register ? .next : .go) .onSubmit { if mode == .register { focusedField = .confirmPassword } else { submit() } } if mode == .register { SecureField("Confirm Password", text: $confirmPassword) .textFieldStyle(.roundedBorder) .focused($focusedField, equals: .confirmPassword) .submitLabel(.go) .onSubmit { submit() } .transition(.opacity.combined(with: .move(edge: .top))) } } .padding(.horizontal, 24) .animation(.easeInOut(duration: 0.2), value: mode) if let error = authStore.error { Text(error) .font(.footnote) .foregroundStyle(.red) .multilineTextAlignment(.center) .padding(.horizontal, 24) .padding(.top, 8) } Button(action: submit) { Group { if authStore.isLoading { ProgressView() .progressViewStyle(.circular) .tint(.white) } else { Text(mode == .login ? "Sign In" : "Create Account") .fontWeight(.semibold) } } .frame(maxWidth: .infinity) .frame(height: 50) } .buttonStyle(.borderedProminent) .tint(.amber) .padding(.horizontal, 24) .padding(.top, 24) .disabled(authStore.isLoading || !formIsValid) Spacer() } .toolbar(.hidden, for: .navigationBar) } .onChange(of: mode) { _, _ in authStore.error = nil confirmPassword = "" } } private var formIsValid: Bool { let base = !username.isEmpty && password.count >= 4 if mode == .register { return base && password == confirmPassword } return base } private func submit() { focusedField = nil Task { if mode == .login { await authStore.login(username: username, password: password) } else { await authStore.register(username: username, password: password) } } } }