/** * Login ViewModel * * Client-side state management for login flow. * Immutable, class-based, contains only UI state. */ export interface LoginFormField { value: string | boolean; error?: string; touched: boolean; validating: boolean; } export interface LoginFormState { fields: { email: LoginFormField; password: LoginFormField; rememberMe: LoginFormField; }; isValid: boolean; isSubmitting: boolean; submitError?: string; submitCount: number; } export interface LoginUIState { showPassword: boolean; showErrorDetails: boolean; } export class LoginViewModel { constructor( public readonly returnTo: string, public readonly hasInsufficientPermissions: boolean, public readonly formState: LoginFormState, public readonly uiState: LoginUIState, public readonly mutationPending: boolean = false, public readonly mutationError: string | null = null ) {} // Immutable updates withFormState(formState: LoginFormState): LoginViewModel { return new LoginViewModel( this.returnTo, this.hasInsufficientPermissions, formState, this.uiState, this.mutationPending, this.mutationError ); } withUIState(uiState: LoginUIState): LoginViewModel { return new LoginViewModel( this.returnTo, this.hasInsufficientPermissions, this.formState, uiState, this.mutationPending, this.mutationError ); } withMutationState(pending: boolean, error: string | null): LoginViewModel { return new LoginViewModel( this.returnTo, this.hasInsufficientPermissions, this.formState, this.uiState, pending, error ); } // Getters for template consumption get showPassword(): boolean { return this.uiState.showPassword; } get showErrorDetails(): boolean { return this.uiState.showErrorDetails; } get isSubmitting(): boolean { return this.formState.isSubmitting || this.mutationPending; } get submitError(): string | undefined { return this.formState.submitError || this.mutationError || undefined; } get formFields() { return this.formState.fields; } }