All files / src/pages/auth LoginPage.tsx

100% Statements 81/81
100% Branches 3/3
100% Functions 2/2
100% Lines 81/81

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 821x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x  
/**
 * @file LoginPage.tsx
 * @description
 * Public login screen for Google OAuth2 SSO (SSO-only UX).
 * Local email/password fields are intentionally removed to avoid confusion.
 *
 * @features
 * - Google SSO button that redirects to backend OAuth2 endpoint.
 * - Error banner if `?error=` is present (e.g., user canceled consent).
 * - Optional: “Continue in Demo Mode” (client-only, read-only KPIs/Analytics).
 *
 * @i18n
 * - Uses react-i18next ('auth' namespace). See /public/locales for JSON files.
 * - Keys used: signIn, welcome, or, signInGoogle, ssoHint, errorTitle, continueDemo.
 */
 
import {
  Box, Card, CardContent, CardHeader, Stack,
  Button, Divider, Typography, Alert
} from '@mui/material';
import GoogleIcon from '@mui/icons-material/Google';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth';
 
export default function LoginPage() {
  const { t } = useTranslation<'auth'>('auth');
  const { search } = useLocation();
  const params = new URLSearchParams(search);
  const oauthError = params.get('error');
 
  const { loginAsDemo, login } = useAuth();
  const navigate = useNavigate();
 
  const handleDemo = () => {
    loginAsDemo();
    navigate('/dashboard', { replace: true });
  };
 
  return (
    <Box sx={{ minHeight: '70vh', display: 'grid', placeItems: 'center', px: 2 }}>
      <Card sx={{ width: 480, maxWidth: '94vw' }}>
        <CardHeader title={t('signIn')} subheader={t('welcome')} />
        <CardContent>
          {oauthError && (
            <Alert severity="error" sx={{ mb: 2 }}>
              <strong>{t('errorTitle')}</strong>
            </Alert>
          )}
 
          <Stack spacing={2}>
            <Button
              type="button"
              variant="outlined"
              startIcon={<GoogleIcon />}
              onClick={login}
              aria-label={t('signInGoogle')}
            >
              {t('signInGoogle')}
            </Button>
 
            <Divider>
              <Typography variant="caption" color="text.secondary" sx={{ textAlign: 'center' }}>
                {t('or')}
              </Typography>
            </Divider>
 
            {/* Demo entry (client-only, read-only) */}
            <Button variant="text" onClick={handleDemo}>
              {t('continueDemo', 'Continue in Demo Mode')}
            </Button>
 
            <Typography variant="caption" color="text.secondary" sx={{ textAlign: 'center' }}>
              {t('ssoHint')}
            </Typography>
          </Stack>
        </CardContent>
      </Card>
    </Box>
  );
}