All files / src/views/auth LoginView.tsx

0% Statements 0/101
100% Branches 1/1
100% Functions 1/1
0% Lines 0/101

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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                                                                                                                                                                                                                       
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useLoginViewModel } from '@/viewmodels/auth/useLoginViewModel';
import { useAuth } from '@/contexts/AuthContext';
import LanguageSwitcher from '@/components/LanguageSwitcher';
import { useTheme } from '@/contexts/ThemeContext';
import SemLogo from '@/components/SemLogo';
import '@/styles/auth.css';
 
export default function LoginView() {
  const { t } = useTranslation();
  const vm = useLoginViewModel();
  const { setSession } = useAuth();
  const { theme } = useTheme();
  const navigate = useNavigate();
 
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const session = await vm.handleLogin();
    if (session) {
      setSession(session.access_token, session.refresh_token, session.user.username, session.user.role);
      navigate('/discover', { replace: true });
    }
  };
 
  return (
    <div className="auth-page">
      <div className="auth-card">
        <div className="auth-card-controls">
          <LanguageSwitcher />
        </div>
        <div className="auth-brand">
          <SemLogo height={76} color={theme === 'dark' ? '#f9fafb' : '#111827'} />
        </div>
        <h1 className="auth-title">{t('auth.login.title')}</h1>
        <p className="auth-subtitle">{t('auth.login.subtitle')}</p>
 
        {vm.apiError && (
          <div className="error-banner" role="alert" aria-live="assertive">
            {vm.apiError}
          </div>
        )}
 
        <form onSubmit={handleSubmit}>
          <div className="field-group">
            <label className="field-label" htmlFor="username">
              {t('auth.login.username')}
            </label>
            <input
              id="username"
              className={`field-input ${vm.errors.username ? 'has-error' : ''}`}
              type="text"
              placeholder={t('auth.login.username_placeholder')}
              value={vm.formData.username}
              onChange={(e) => vm.updateField('username', e.target.value)}
              autoComplete="username"
              disabled={vm.isLoading}
              aria-invalid={!!vm.errors.username}
              aria-describedby={vm.errors.username ? 'username-error' : undefined}
            />
            {vm.errors.username && (
              <p className="field-error" id="username-error" role="alert">
                {vm.errors.username}
              </p>
            )}
          </div>
 
          <div className="field-group">
            <label className="field-label" htmlFor="password">
              {t('auth.login.password')}
            </label>
            <input
              id="password"
              className={`field-input ${vm.errors.password ? 'has-error' : ''}`}
              type="password"
              placeholder={t('auth.login.password_placeholder')}
              value={vm.formData.password}
              onChange={(e) => vm.updateField('password', e.target.value)}
              autoComplete="current-password"
              disabled={vm.isLoading}
              aria-invalid={!!vm.errors.password}
              aria-describedby={vm.errors.password ? 'password-error' : undefined}
            />
            {vm.errors.password && (
              <p className="field-error" id="password-error" role="alert">
                {vm.errors.password}
              </p>
            )}
            <div style={{ textAlign: 'right', marginTop: '0.5rem', marginBottom: '1rem' }}>
              <Link to="/forgot-password" className="link" style={{ fontSize: '0.875rem' }}>
                {t('auth.login.forgot_password')}
              </Link>
            </div>
          </div>
 
          <button
            type="submit"
            className="btn-primary"
            disabled={vm.isLoading}
          >
            {vm.isLoading ? <span className="spinner" /> : t('auth.login.submit')}
          </button>
        </form>
 
        <div className="auth-footer">
          <span>{t('auth.login.signup_prompt')}</span>
          <Link to="/register" className="link">
            {t('auth.login.signup_link')}
          </Link>
        </div>
      </div>
    </div>
  );
}