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 | 1x 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>
);
}
|