fixed login, removed old makeStyles call

This commit is contained in:
Roman 2024-04-05 17:35:52 +02:00
parent c6f9c8894c
commit b68ff85578
4 changed files with 58 additions and 457 deletions

@ -1,54 +0,0 @@
import React, {useCallback, useContext, useState} from 'react';
import {Box} from "@mui/material";
import {LocaleContext} from "shared/locale";
/*
const useStyles = makeStyles((theme) => ({
languageFlag: {
margin: theme.spacing(0.2),
cursor: "pointer",
border: 0,
}
}));
*/
export default function LanguageSelection(props) {
const api = props.api;
// const classes = useStyles();
const classes = {};
const [languages, setLanguages] = useState(null);
const {translate: L, setLanguageByCode} = useContext(LocaleContext);
const onSetLanguage = useCallback((code) => {
setLanguageByCode(api, code).then((res) => {
if (!res.success) {
alert(res.msg);
}
});
}, []);
let flags = [];
if (languages === null) {
api.getLanguages().then((res) => {
if (res.success) {
setLanguages(res.languages);
} else {
setLanguages({});
alert(res.msg);
}
});
} else {
for (const language of Object.values(languages)) {
let key = `lang-${language.code}`;
flags.push(<button type={"button"} title={language.name} onClick={() => onSetLanguage(language.code)}
key={key} className={classes.languageFlag} >
<img alt={key} src={`/img/icons/lang/${language.code}.gif`} />
</button>);
}
}
return <Box mt={1}>
{L("general.language") + ": "} { flags }
</Box>
}

@ -1,310 +0,0 @@
import {
Box,
Button,
Checkbox, CircularProgress, Container,
FormControlLabel,
Grid,
Link,
TextField,
Typography
} from "@mui/material";
import {Alert} from '@mui/lab';
import React, {useCallback, useContext, useEffect, useState} from "react";
import ReplayIcon from '@mui/icons-material';
import LanguageSelection from "../elements/language-selection";
import {decodeText, encodeText, getParameter, removeParameter} from "shared/util";
import Icon from "shared/elements/icon";
import {LocaleContext} from "shared/locale";
/*
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(2),
width: "60px",
height: "60px"
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
logo: {
marginRight: theme.spacing(3)
},
headline: {
width: "100%",
},
container: {
marginTop: theme.spacing(5),
paddingBottom: theme.spacing(1),
borderColor: theme.palette.primary.main,
borderStyle: "solid",
borderWidth: 1,
borderRadius: 5
},
buttons2FA: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
error2FA: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
"& > div": {
fontSize: 16
},
"& > button": {
marginTop: theme.spacing(1)
}
}
}));
*/
export default function LoginForm(props) {
const api = props.api;
// const classes = useStyles();
const classes = {};
let [username, setUsername] = useState("");
let [password, setPassword] = useState("");
let [rememberMe, setRememberMe] = useState(true);
let [isLoggingIn, setLoggingIn] = useState(false);
let [emailConfirmed, setEmailConfirmed] = useState(null);
let [tfaCode, set2FACode] = useState("");
let [tfaState, set2FAState] = useState(0); // 0: not sent, 1: sent, 2: retry
let [tfaError, set2FAError] = useState("");
let [error, setError] = useState("");
let [loaded, setLoaded] = useState(false);
const {translate: L, currentLocale, requestModules} = useContext(LocaleContext);
const onUpdateLocale = useCallback(() => {
requestModules(api, ["general", "account"], currentLocale).then(data => {
setLoaded(true);
if (!data.success) {
alert(data.msg);
}
});
}, [currentLocale]);
useEffect(() => {
onUpdateLocale();
}, [currentLocale]);
const onLogin = useCallback(() => {
if (!isLoggingIn) {
setError("");
setLoggingIn(true);
removeParameter("success");
api.login(username, password, rememberMe).then((res) => {
set2FAState(0);
setLoggingIn(false);
setPassword("");
if (!res.success) {
setEmailConfirmed(res.emailConfirmed);
setError(res.msg);
} else {
props.onLogin();
}
});
}
}, [api, isLoggingIn, password, props, rememberMe, username]);
const onSubmit2FA = useCallback(() => {
setLoggingIn(true);
props.onTotp2FA(tfaCode, (res) => {
setLoggingIn(false);
});
}, [tfaCode, props]);
const onCancel2FA = useCallback(() => {
props.onLogout();
}, [props]);
useEffect(() => {
if (!api.loggedIn || !api.user) {
return;
}
let twoFactor = api.user["2fa"];
if (!twoFactor || !twoFactor.confirmed ||
twoFactor.authenticated || twoFactor.type !== "fido") {
return;
}
if (tfaState === 0) {
set2FAState(1);
set2FAError("");
navigator.credentials.get({
publicKey: {
challenge: encodeText(window.atob(twoFactor.challenge)),
allowCredentials: [{
id: encodeText(window.atob(twoFactor.credentialID)),
type: "public-key",
}],
userVerification: "discouraged",
},
}).then((res) => {
let credentialID = res.id;
let clientDataJson = decodeText(res.response.clientDataJSON);
let authData = window.btoa(decodeText(res.response.authenticatorData));
let signature = window.btoa(decodeText(res.response.signature));
props.onKey2FA(credentialID, clientDataJson, authData, signature, res => {
if (!res.success) {
set2FAState(2);
}
});
}).catch(e => {
set2FAState(2);
set2FAError(e.toString());
});
}
}, [api.loggedIn, api.user, tfaState, props]);
const createForm = () => {
// 2FA
if (api.loggedIn && api.user["2fa"]) {
return <>
<div>{L("account.2fa_title")}: {api.user["2fa"].type}</div>
{ api.user["2fa"].type === "totp" ?
<TextField
variant="outlined" margin="normal"
id="code" label={L("account.6_digit_code")} name="code"
autoComplete="code"
required fullWidth autoFocus
value={tfaCode} onChange={(e) => set2FACode(e.target.value)}
/> : <>
{L("account.2fa_text")}
<Box mt={2} textAlign={"center"}>
{tfaState !== 2
? <CircularProgress/>
: <div className={classes.error2FA}>
<div>{L("general.something_went_wrong")}:<br />{tfaError}</div>
<Button onClick={() => set2FAState(0)}
variant={"outlined"} color={"secondary"} size={"small"}>
<ReplayIcon />&nbsp;{L("general.retry")}
</Button>
</div>
}
</Box>
</>
}
{
error ? <Alert severity="error">{error}</Alert> : <></>
}
<Grid container spacing={2} className={classes.buttons2FA}>
<Grid item xs={6}>
<Button
fullWidth variant="contained"
color="inherit" size={"medium"}
disabled={isLoggingIn}
onClick={onCancel2FA}>
{L("general.go_back")}
</Button>
</Grid>
<Grid item xs={6}>
<Button
type="submit" fullWidth variant="contained"
color="primary" size={"medium"}
disabled={isLoggingIn || api.user["2fa"].type !== "totp"}
onClick={onSubmit2FA}>
{isLoggingIn ?
<>{L("general.submitting")} <CircularProgress size={15}/></> :
L("general.submit")
}
</Button>
</Grid>
</Grid>
</>
}
return <>
<TextField
variant="outlined" margin="normal"
id="username" label={L("account.username")} name="username"
autoComplete="username" disabled={isLoggingIn}
required fullWidth autoFocus
value={username} onChange={(e) => setUsername(e.target.value)}
/>
<TextField
variant="outlined" margin="normal"
name="password" label={L("account.password")} type="password" id="password"
autoComplete="current-password"
required fullWidth disabled={isLoggingIn}
value={password} onChange={(e) => setPassword(e.target.value)}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary"/>}
label={L("account.remember_me")}
checked={rememberMe} onClick={(e) => setRememberMe(!rememberMe)}
/>
{
error
? <Alert severity="error">
{error}
{emailConfirmed === false
? <> <Link href={"/resendConfirmEmail"}>Click here</Link> to resend the confirmation email.</>
: <></>
}
</Alert>
: (successMessage
? <Alert severity="success">{successMessage}</Alert>
: <></>)
}
<Button
type={"submit"} fullWidth variant={"contained"}
color={"primary"} className={classes.submit}
size={"large"}
disabled={isLoggingIn}
onClick={onLogin}>
{isLoggingIn ?
<>{L("account.signing_in")} <CircularProgress size={15}/></> :
L("account.sign_in")
}
</Button>
<Grid container>
<Grid item xs>
<Link href="/resetPassword" variant="body2">
{L("account.forgot_password")}
</Link>
</Grid>
{ props.info.registrationAllowed ?
<Grid item>
<Link href="/register" variant="body2">
{L("account.register_text")}
</Link>
</Grid> : <></>
}
</Grid>
</>
}
if (!loaded) {
return <b>{L("general.loading")} <Icon icon={"spinner"}/></b>
}
let successMessage = getParameter("success");
return <Container maxWidth={"xs"} className={classes.container}>
<div className={classes.paper}>
<div className={classes.headline}>
<Typography component="h1" variant="h4">
<img src={"/img/icons/logo.png"} alt={"Logo"} height={48} className={classes.logo}/>
{props.info.siteName}
</Typography>
</div>
<form className={classes.form} onSubmit={(e) => e.preventDefault()}>
{ createForm() }
<LanguageSelection api={api} />
</form>
</div>
</Container>
}

@ -1,16 +1,12 @@
import React, {useCallback, useContext, useState} from 'react';
import {Box, Button} from "@mui/material";
import {Box, styled} from "@mui/material";
import {LocaleContext} from "shared/locale";
/*
const useStyles = makeStyles((theme) => ({
languageFlag: {
margin: theme.spacing(0.2),
cursor: "pointer",
border: 0,
}
const LanguageFlag = styled(Box)((props) => ({
display: "inline-block",
marginRight: props.theme.spacing(0.5),
cursor: "pointer"
}));
*/
export default function LanguageSelection(props) {
@ -39,10 +35,10 @@ export default function LanguageSelection(props) {
} else {
for (const language of Object.values(languages)) {
let key = `lang-${language.code}`;
flags.push(<Button type={"button"} title={language.name} onClick={() => onSetLanguage(language.code)}
key={key} >
<img alt={key} src={`/img/icons/lang/${language.code}.gif`} />
</Button>);
flags.push(<LanguageFlag key={key}>
<img alt={key} src={`/img/icons/lang/${language.code}.gif`} onClick={() => onSetLanguage(language.code)} />
</LanguageFlag>
);
}
}

@ -1,76 +1,43 @@
import {Box,
import {
Box,
Button,
Checkbox, CircularProgress, Container,
FormControlLabel,
Grid,
Link,
Link, styled,
TextField,
Typography
} from "@mui/material";
import {Alert} from '@mui/lab';
import React, {useCallback, useContext, useEffect, useState} from "react";
import React, {useCallback, useContext, useEffect, useRef, useState} from "react";
import ReplayIcon from '@mui/icons-material/Replay';
import LanguageSelection from "../elements/language-selection";
import {decodeText, encodeText, getParameter, removeParameter} from "shared/util";
import {LocaleContext} from "shared/locale";
/*
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(2),
const LoginContainer = styled(Container)((props) => ({
marginTop: props.theme.spacing(5),
paddingBottom: props.theme.spacing(1),
borderColor: props.theme.palette.primary.main,
borderStyle: "solid",
borderWidth: 1,
borderRadius: 5,
"& h1 > img": {
marginRight: props.theme.spacing(2),
width: "60px",
height: "60px"
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
logo: {
marginRight: theme.spacing(3)
},
headline: {
width: "100%",
},
container: {
marginTop: theme.spacing(5),
paddingBottom: theme.spacing(1),
borderColor: theme.palette.primary.main,
borderStyle: "solid",
borderWidth: 1,
borderRadius: 5
},
buttons2FA: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
error2FA: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
"& > div": {
fontSize: 16
},
"& > button": {
marginTop: theme.spacing(1)
}
}
}));
*/
const ResponseAlert = styled(Alert)((props) => ({
marginBottom: props.theme.spacing(2),
}));
export default function LoginForm(props) {
const api = props.api;
// const classes = useStyles();
const classes = { };
// inputs
let [username, setUsername] = useState("");
@ -91,6 +58,9 @@ export default function LoginForm(props) {
let [isLoggingIn, setLoggingIn] = useState(false);
let [loaded, setLoaded] = useState(false);
// ui
let passwordRef = useRef();
const {translate: L, currentLocale, requestModules} = useContext(LocaleContext);
const onUpdateLocale = useCallback(() => {
@ -203,9 +173,9 @@ export default function LoginForm(props) {
value={tfaCode} onChange={(e) => set2FACode(e.target.value)}
/>
{
tfaToken.error ? <Alert severity="error">{tfaToken.error}</Alert> : <></>
tfaToken.error ? <ResponseAlert severity="error">{tfaToken.error}</ResponseAlert> : <></>
}
<Grid container spacing={2} className={classes.buttons2FA}>
<Grid container spacing={2}>
<Grid item xs={6}>
<Button
fullWidth variant={"contained"}
@ -237,16 +207,16 @@ export default function LoginForm(props) {
<Box mt={2} textAlign={"center"}>
{tfaToken.step !== 2
? <CircularProgress/>
: <div className={classes.error2FA}>
: <Box>
<div><b>{L("general.something_went_wrong")}:</b><br />{tfaToken.error}</div>
<Button onClick={() => set2FAToken({ ...tfaToken, step: 0, error: "" })}
variant={"outlined"} color={"secondary"} size={"small"}>
<ReplayIcon />&nbsp;{L("general.retry")}
</Button>
</div>
</Box>
}
</Box>
<Grid container spacing={2} className={classes.buttons2FA}>
<Grid container spacing={2}>
<Grid item xs={6}>
<Button
fullWidth variant={"contained"}
@ -275,18 +245,21 @@ export default function LoginForm(props) {
return <>
<TextField
variant="outlined" margin="normal"
id="username" label={L("account.username")} name="username"
autoComplete="username" disabled={isLoggingIn}
variant={"outlined"} margin={"normal"}
label={L("account.username")} name={"username"}
autoComplete={"username"} disabled={isLoggingIn}
required fullWidth autoFocus
value={username} onChange={(e) => setUsername(e.target.value)}
onKeyDown={e => e.key === "Enter" && passwordRef.current && passwordRef.current.focus()}
/>
<TextField
variant="outlined" margin="normal"
name="password" label={L("account.password")} type="password" id="password"
autoComplete="current-password"
variant={"outlined"} margin={"normal"}
name={"password"} label={L("account.password")} type={"password"}
autoComplete={"current-password"}
required fullWidth disabled={isLoggingIn}
value={password} onChange={(e) => setPassword(e.target.value)}
onKeyDown={e => e.key === "Enter" && onLogin()}
inputRef={passwordRef}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary"/>}
@ -295,20 +268,20 @@ export default function LoginForm(props) {
/>
{
error
? <Alert severity="error">
? <ResponseAlert severity={"error"}>
{error}
{emailConfirmed === false
? <> <Link href={"/resendConfirmEmail"}>Click here</Link> to resend the confirmation email.</>
: <></>
}
</Alert>
</ResponseAlert>
: (successMessage
? <Alert severity="success">{successMessage}</Alert>
? <ResponseAlert severity={"success"}>{successMessage}</ResponseAlert>
: <></>)
}
<Button
type={"submit"} fullWidth variant={"contained"}
color={"primary"} className={classes.submit}
color={"primary"}
size={"large"}
disabled={isLoggingIn}
onClick={onLogin}>
@ -342,18 +315,14 @@ export default function LoginForm(props) {
}
let successMessage = getParameter("success");
return <Container maxWidth={"xs"} className={classes.container}>
<div className={classes.paper}>
<div className={classes.headline}>
<Typography component="h1" variant="h4">
<img src={"/img/icons/logo.png"} alt={"Logo"} height={48} className={classes.logo}/>
{props.info.siteName}
</Typography>
</div>
<form className={classes.form} onSubmit={(e) => e.preventDefault()}>
{ createForm() }
<LanguageSelection api={api} />
</form>
</div>
</Container>
return <LoginContainer maxWidth={"xs"}>
<Box mt={2}>
<Typography component="h1" variant="h4">
<img src={"/img/icons/logo.png"} alt={"Logo"} height={48} />
{props.info.siteName}
</Typography>
{ createForm() }
<LanguageSelection api={api} />
</Box>
</LoginContainer>
}