Localization & stuff

This commit is contained in:
2022-11-30 16:42:24 +01:00
parent 25ef07b0b7
commit 1ba27e4f40
54 changed files with 856 additions and 494 deletions

View File

@@ -2,10 +2,11 @@
"name": "admin-panel",
"version": "1.0.0",
"dependencies": {
"shared": "link:../shared"
"shared": "link:../shared",
"react": "^18.2.0"
},
"scripts": {
"debug": "react-app-rewired start"
"dev": "react-app-rewired start"
},
"author": "",
"license": "ISC",

View File

@@ -3,7 +3,7 @@ import './res/adminlte.min.css';
import './res/index.css';
import API from "shared/api";
import Icon from "shared/elements/icon";
import {BrowserRouter, Route, Routes} from "react-router-dom";
import {BrowserRouter, Routes} from "react-router-dom";
import Dialog from "./elements/dialog";
import Footer from "./elements/footer";
import Header from "./elements/header";
@@ -11,12 +11,14 @@ import Sidebar from "./elements/sidebar";
import LoginForm from "./views/login";
import {Alert} from "@material-ui/lab";
import {Button} from "@material-ui/core";
import {Locale} from "shared/locale";
export default class AdminDashboard extends React.Component {
constructor(props) {
super(props);
this.api = new API();
this.locale = Locale.getInstance();
this.state = {
loaded: false,
dialog: { onClose: () => this.hideDialog() },
@@ -39,13 +41,19 @@ export default class AdminDashboard extends React.Component {
onInit() {
this.setState({ ...this.state, loaded: false, error: null });
this.api.info().then(data => {
this.api.getLanguageEntries("general").then(data => {
if (data.success) {
this.setState({...this.state, info: data.info })
this.api.fetchUser().then(data => {
this.api.info().then(data => {
if (data.success) {
setInterval(this.onUpdate.bind(this), 60*1000);
this.setState({...this.state, loaded: true});
this.setState({...this.state, info: data.info })
this.api.fetchUser().then(data => {
if (data.success) {
setInterval(this.onUpdate.bind(this), 60*1000);
this.setState({...this.state, loaded: true});
} else {
this.setState({ ...this.state, error: data.msg })
}
});
} else {
this.setState({ ...this.state, error: data.msg })
}
@@ -73,7 +81,6 @@ export default class AdminDashboard extends React.Component {
callback(res);
})
} else {
this.setState({ ...this.state, error: res.msg });
callback(res);
}
});
@@ -96,7 +103,6 @@ export default class AdminDashboard extends React.Component {
callback(res);
})
} else {
this.setState({ ...this.state, error: res.msg });
callback(res);
}
});
@@ -111,7 +117,6 @@ export default class AdminDashboard extends React.Component {
callback(res);
})
} else {
this.setState({ ...this.state, error: res.msg });
callback(res);
}
});
@@ -136,6 +141,7 @@ export default class AdminDashboard extends React.Component {
showDialog: this.showDialog.bind(this),
api: this.api,
info: this.state.info,
locale: this.locale,
onUpdateLocale: this.onUpdateLocale.bind(this),
onLogout: this.onLogout.bind(this),
onLogin: this.onLogin.bind(this),

View File

@@ -1,5 +1,5 @@
import React, {useState} from 'react';
import {initLocale, L} from "shared/locale/locale";
import {L} from "shared/locale";
import {Box} from "@material-ui/core";
import {makeStyles} from "@material-ui/core/styles";
@@ -20,8 +20,9 @@ export default function LanguageSelection(props) {
const onSetLanguage = (code) => {
api.setLanguageByCode(code).then((res) => {
if (res.success) {
initLocale(code);
props.onUpdateLocale();
} else {
alert(res.msg);
}
});
};
@@ -42,6 +43,6 @@ export default function LanguageSelection(props) {
}
return <Box mt={1}>
{L("Language") + ": "} { flags }
{L("general.language") + ": "} { flags }
</Box>
}

View File

@@ -1,8 +1,6 @@
import ReactDOM from "react-dom";
import React from "react";
import {createRoot} from "react-dom/client";
import AdminDashboard from "./App";
ReactDOM.render(
<AdminDashboard />,
document.getElementById('admin-panel')
);
const root = createRoot(document.getElementById('admin-panel'));
root.render(<AdminDashboard />);

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -13,10 +13,11 @@ import {makeStyles} from '@material-ui/core/styles';
import {Alert} from '@material-ui/lab';
import React, {useCallback, useEffect, useState} from "react";
import {Navigate} from "react-router-dom";
import {L} from "shared/locale/locale";
import {L} from "shared/locale";
import ReplayIcon from '@material-ui/icons/Replay';
import LanguageSelection from "../elements/language-selection";
import {decodeText, encodeText, getParameter, removeParameter} from "shared/util";
import Icon from "shared/elements/icon";
const useStyles = makeStyles((theme) => ({
paper: {
@@ -79,13 +80,29 @@ export default function LoginForm(props) {
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(0);
const getNextUrl = () => {
return getParameter("next") || "/admin";
}
const onUpdateLocale = useCallback(() => {
api.getLanguageEntries(["general", "account"]).then(data => {
setLoaded(loaded + 1);
if (!data.success) {
alert(data.msg);
}
});
}, [loaded]);
useEffect(() => {
onUpdateLocale();
}, []);
const onLogin = useCallback(() => {
if (!isLoggingIn) {
setError("");
setLoggingIn(true);
removeParameter("success");
props.onLogin(username, password, rememberMe, (res) => {
@@ -94,6 +111,7 @@ export default function LoginForm(props) {
setPassword("");
if (!res.success) {
setEmailConfirmed(res.emailConfirmed);
setError(res.msg);
}
});
}
@@ -162,24 +180,24 @@ export default function LoginForm(props) {
// 2FA
if (api.loggedIn && api.user["2fa"]) {
return <>
<div>Additional information is required for logging in: {api.user["2fa"].type}</div>
<div>{L("account.2fa_title")}: {api.user["2fa"].type}</div>
{ api.user["2fa"].type === "totp" ?
<TextField
variant="outlined" margin="normal"
id="code" label={L("6-Digit Code")} name="code"
id="code" label={L("account.6_digit_code")} name="code"
autoComplete="code"
required fullWidth autoFocus
value={tfaCode} onChange={(e) => set2FACode(e.target.value)}
/> : <>
Plugin your 2FA-Device. Interaction might be required, e.g. typing in a PIN or touching it.
{L("account.2fa_text")}
<Box mt={2} textAlign={"center"}>
{tfaState !== 2
? <CircularProgress/>
: <div className={classes.error2FA}>
<div>{L("Something went wrong:")}<br />{tfaError}</div>
<div>{L("general.something_went_wrong")}:<br />{tfaError}</div>
<Button onClick={() => set2FAState(0)}
variant={"outlined"} color={"secondary"} size={"small"}>
<ReplayIcon />&nbsp;{L("Retry")}
<ReplayIcon />&nbsp;{L("general.retry")}
</Button>
</div>
}
@@ -187,7 +205,7 @@ export default function LoginForm(props) {
</>
}
{
props.error ? <Alert severity="error">{props.error}</Alert> : <></>
error ? <Alert severity="error">{error}</Alert> : <></>
}
<Grid container spacing={2} className={classes.buttons2FA}>
<Grid item xs={6}>
@@ -196,7 +214,7 @@ export default function LoginForm(props) {
color="inherit" size={"medium"}
disabled={isLoggingIn}
onClick={onCancel2FA}>
{L("Go back")}
{L("general.go_back")}
</Button>
</Grid>
<Grid item xs={6}>
@@ -206,8 +224,8 @@ export default function LoginForm(props) {
disabled={isLoggingIn || api.user["2fa"].type !== "totp"}
onClick={onSubmit2FA}>
{isLoggingIn ?
<>{L("Submitting")} <CircularProgress size={15}/></> :
L("Submit")
<>{L("general.submitting")} <CircularProgress size={15}/></> :
L("general.submit")
}
</Button>
</Grid>
@@ -218,35 +236,35 @@ export default function LoginForm(props) {
return <>
<TextField
variant="outlined" margin="normal"
id="username" label={L("Username")} name="username"
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("Password")} type="password" id="password"
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("Remember me")}
label={L("account.remember_me")}
checked={rememberMe} onClick={(e) => setRememberMe(!rememberMe)}
/>
{
props.error ?
<Alert severity="error">
{props.error}
error
? <Alert severity="error">
{error}
{emailConfirmed === false
? <> <Link href={"/resendConfirmation"}>Click here</Link> to resend the confirmation email.</>
: <></>
}
</Alert> :
successMessage
</Alert>
: (successMessage
? <Alert severity="success">{successMessage}</Alert>
: <></>
: <></>)
}
<Button
type={"submit"} fullWidth variant={"contained"}
@@ -255,20 +273,20 @@ export default function LoginForm(props) {
disabled={isLoggingIn}
onClick={onLogin}>
{isLoggingIn ?
<>{L("Signing in")} <CircularProgress size={15}/></> :
L("Sign In")
<>{L("account.signing_in")} <CircularProgress size={15}/></> :
L("account.sign_in")
}
</Button>
<Grid container>
<Grid item xs>
<Link href="/resetPassword" variant="body2">
{L("Forgot password?")}
{L("account.forgot_password")}
</Link>
</Grid>
{ props.info.registrationAllowed ?
<Grid item>
<Link href="/register" variant="body2">
{L("Don't have an account? Sign Up")}
{L("account.register_text")}
</Link>
</Grid> : <></>
}
@@ -276,6 +294,10 @@ export default function LoginForm(props) {
</>
}
if (loaded === 0) {
return <b>{L("general.loading")} <Icon icon={"spinner"}/></b>
}
let successMessage = getParameter("success");
return <Container maxWidth={"xs"} className={classes.container}>
<div className={classes.paper}>
@@ -287,7 +309,7 @@ export default function LoginForm(props) {
</div>
<form className={classes.form} onSubmit={(e) => e.preventDefault()}>
{ createForm() }
<LanguageSelection api={api} locale={props.locale} onUpdateLocale={props.onUpdateLocale}/>
<LanguageSelection api={api} onUpdateLocale={onUpdateLocale} />
</form>
</div>
</Container>