import React, { useEffect, useState } from 'react'; import dayjs from 'dayjs'; import { useMediaQuery, InputLabel, Select, MenuItem, FormControl, Button, TextField, Link, Snackbar, IconButton, Tooltip, LinearProgress, Box, } from '@mui/material'; import ReactCountryFlag from 'react-country-flag'; import makeStyles from '@mui/styles/makeStyles'; import CloseIcon from '@mui/icons-material/Close'; import LockOpenIcon from '@mui/icons-material/LockOpen'; import { useTheme } from '@mui/material/styles'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import { sessionActions } from '../store'; import { useLocalization, useTranslation } from '../common/components/LocalizationProvider'; import LoginLayout from './LoginLayout'; import usePersistedState from '../common/util/usePersistedState'; import { handleLoginTokenListeners, nativeEnvironment, nativePostMessage } from '../common/components/NativeInterface'; import LogoImage from './LogoImage'; import { useCatch } from '../reactHelper'; const useStyles = makeStyles((theme) => ({ options: { position: 'fixed', top: theme.spacing(1), right: theme.spacing(1), }, container: { display: 'flex', flexDirection: 'column', gap: theme.spacing(2), }, extraContainer: { display: 'flex', gap: theme.spacing(2), }, registerButton: { minWidth: 'unset', }, resetPassword: { cursor: 'pointer', textAlign: 'center', marginTop: theme.spacing(2), }, })); const LoginPage = () => { const classes = useStyles(); const dispatch = useDispatch(); const navigate = useNavigate(); const theme = useTheme(); const t = useTranslation(); const { languages, language, setLanguage } = useLocalization(); const languageList = Object.entries(languages).map((values) => ({ code: values[0], country: values[1].country, name: values[1].name })); const [failed, setFailed] = useState(false); const [email, setEmail] = usePersistedState('loginEmail', ''); const [password, setPassword] = useState(''); const [code, setCode] = useState(''); const registrationEnabled = useSelector((state) => state.session.server.registration); const languageEnabled = useSelector((state) => !state.session.server.attributes['ui.disableLoginLanguage']); const changeEnabled = useSelector((state) => !state.session.server.attributes.disableChange); const emailEnabled = useSelector((state) => state.session.server.emailEnabled); const openIdEnabled = useSelector((state) => state.session.server.openIdEnabled); const openIdForced = useSelector((state) => state.session.server.openIdEnabled && state.session.server.openIdForce); const [codeEnabled, setCodeEnabled] = useState(false); const [announcementShown, setAnnouncementShown] = useState(false); const announcement = useSelector((state) => state.session.server.announcement); const generateLoginToken = async () => { if (nativeEnvironment) { let token = ''; try { const expiration = dayjs().add(6, 'months').toISOString(); const response = await fetch('/api/session/token', { method: 'POST', body: new URLSearchParams(`expiration=${expiration}`), }); if (response.ok) { token = await response.text(); } } catch (error) { token = ''; } nativePostMessage(`login|${token}`); } }; const handlePasswordLogin = async (event) => { event.preventDefault(); setFailed(false); try { const query = `email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`; const response = await fetch('/api/session', { method: 'POST', body: new URLSearchParams(code.length ? query + `&code=${code}` : query), }); if (response.ok) { const user = await response.json(); generateLoginToken(); dispatch(sessionActions.updateUser(user)); navigate('/'); } else if (response.status === 401 && response.headers.get('WWW-Authenticate') === 'TOTP') { setCodeEnabled(true); } else { throw Error(await response.text()); } } catch (error) { setFailed(true); setPassword(''); } }; const handleTokenLogin = useCatch(async (token) => { const response = await fetch(`/api/session?token=${encodeURIComponent(token)}`); if (response.ok) { const user = await response.json(); dispatch(sessionActions.updateUser(user)); navigate('/'); } else { throw Error(await response.text()); } }); const handleSpecialKey = (e) => { if (e.keyCode === 13 && email && password && (!codeEnabled || code)) { handlePasswordLogin(e); } }; const handleOpenIdLogin = () => { document.location = '/api/session/openid/auth'; }; useEffect(() => nativePostMessage('authentication'), []); useEffect(() => { const listener = (token) => handleTokenLogin(token); handleLoginTokenListeners.add(listener); return () => handleLoginTokenListeners.delete(listener); }, []); if (openIdForced) { handleOpenIdLogin(); return (); } return (
{nativeEnvironment && changeEnabled && ( navigate('/change-server')}> )}
{useMediaQuery(theme.breakpoints.down('lg')) && } setEmail(e.target.value)} onKeyUp={handleSpecialKey} helperText={failed && 'Invalid username or password'} /> setPassword(e.target.value)} onKeyUp={handleSpecialKey} /> {codeEnabled && ( setCode(e.target.value)} onKeyUp={handleSpecialKey} /> )} {openIdEnabled && ( )}
{languageEnabled && ( {t('loginLanguage')} )}
{emailEnabled && ( navigate('/reset-password')} className={classes.resetPassword} underline="none" variant="caption" > {t('loginReset')} )}
setAnnouncementShown(true)}> )} />
); }; export default LoginPage;