All files / src/viewmodels/auth useLoginViewModel.ts

100% Statements 33/33
100% Branches 4/4
100% Functions 6/6
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 891x 1x   1x 1x                                               1x         1x 21x 21x 21x 21x   21x   11x 11x 11x         21x 7x         12x 7x 2x 2x     5x 5x 5x 5x         4x 3x   1x   4x   5x       21x                  
import { useState, useCallback } from 'react';
import { login } from '@/services/authService';
import { AuthSessionResponse } from '@/models/auth';
import { ApiError } from '@/services/api';
import { validateUsername, validatePassword } from '@/utils/validators';
 
export interface LoginFormData {
  username: string;
  password: string;
}
 
export interface LoginFormErrors {
  username?: string | null;
  password?: string | null;
}
 
export interface LoginViewModel {
  formData: LoginFormData;
  errors: LoginFormErrors;
  isLoading: boolean;
  apiError: string | null;
  updateField: <K extends keyof LoginFormData>(
    field: K,
    value: LoginFormData[K],
  ) => void;
  handleLogin: () => Promise<AuthSessionResponse | null>;
}
 
const INITIAL_FORM_DATA: LoginFormData = {
  username: '',
  password: '',
};
 
export function useLoginViewModel(): LoginViewModel {
  const [formData, setFormData] = useState<LoginFormData>(INITIAL_FORM_DATA);
  const [errors, setErrors] = useState<LoginFormErrors>({});
  const [isLoading, setIsLoading] = useState(false);
  const [apiError, setApiError] = useState<string | null>(null);
 
  const updateField = useCallback(
    <K extends keyof LoginFormData>(field: K, value: LoginFormData[K]) => {
      setFormData((prev) => ({ ...prev, [field]: value }));
      setErrors((prev) => ({ ...prev, [field]: null }));
      setApiError(null);
    },
    [],
  );
 
  const handleLogin = useCallback(async (): Promise<AuthSessionResponse | null> => {
    const newErrors: LoginFormErrors = {
      username: validateUsername(formData.username),
      password: validatePassword(formData.password),
    };
 
    const hasErrors = Object.values(newErrors).some((e) => e != null);
    if (hasErrors) {
      setErrors(newErrors);
      return null;
    }
 
    setIsLoading(true);
    setApiError(null);
    try {
      return await login({
        username: formData.username,
        password: formData.password,
      });
    } catch (err) {
      if (err instanceof ApiError) {
        setApiError(err.message);
      } else {
        setApiError('An unexpected error occurred. Please try again.');
      }
      return null;
    } finally {
      setIsLoading(false);
    }
  }, [formData]);
 
  return {
    formData,
    errors,
    isLoading,
    apiError,
    updateField,
    handleLogin,
  };
}