Can request admin settings update

This commit is contained in:
Pierre HUBERT 2021-05-13 16:58:50 +02:00
parent d02d025418
commit 6dde0f40c4
5 changed files with 428 additions and 206 deletions

View File

@ -17,6 +17,12 @@ export interface AdminAccount {
time_create: number; time_create: number;
} }
export interface NewAdminGeneralSettings {
id: number;
name: string;
email: string;
}
const SESSION_STORAGE_TOKEN = "auth_token"; const SESSION_STORAGE_TOKEN = "auth_token";
let currentAccount: AdminAccount | null = null; let currentAccount: AdminAccount | null = null;
@ -114,4 +120,17 @@ export class AccountHelper {
sessionStorage.removeItem(SESSION_STORAGE_TOKEN); sessionStorage.removeItem(SESSION_STORAGE_TOKEN);
document.location.href = document.location.href + ""; document.location.href = document.location.href + "";
} }
/**
* Update an admin's settings
*
* @param a New settings
*/
static async UpdateGeneralSettings(s: NewAdminGeneralSettings) {
await serverRequest("admins/update_general_settings", {
id: s.id,
name: s.name,
email: s.email,
});
}
} }

View File

@ -4,10 +4,19 @@
* @author Pierre Hubert * @author Pierre Hubert
*/ */
import {
Button,
Divider,
Grid,
Paper,
TextField,
Typography,
} from "@material-ui/core";
import React from "react"; import React from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper"; import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { matAlert, snackbar } from "../widgets/DialogsProvider";
export function AccountSettingsRoute() { export function AccountSettingsRoute() {
let params: any = useParams(); let params: any = useParams();
@ -52,6 +61,110 @@ class AccountSettingsRouteInner extends React.Component<
} }
build() { build() {
return <p>{this.state.account.email}</p>; return (
<Grid container spacing={2}>
<GeneralSettings admin={this.state.account}></GeneralSettings>
</Grid>
);
} }
} }
class GeneralSettings extends React.Component<
{ admin: AdminAccount },
{ newName: string; newEmail: string }
> {
constructor(p: any) {
super(p);
this.state = {
newName: this.props.admin.name,
newEmail: this.props.admin.email,
};
this.changedName = this.changedName.bind(this);
this.changedEmail = this.changedEmail.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
changedName(e: React.ChangeEvent<HTMLInputElement>) {
this.setState({ newName: e.target.value });
}
changedEmail(e: React.ChangeEvent<HTMLInputElement>) {
this.setState({ newEmail: e.target.value });
}
get isValid(): boolean {
return this.state.newEmail.length > 2 && this.state.newName.length > 2;
}
async handleSubmit() {
try {
if (!this.isValid) return;
await AccountHelper.UpdateGeneralSettings({
id: this.props.admin.id,
name: this.state.newName,
email: this.state.newEmail,
});
snackbar("Successfully updated admin settings!");
} catch (e) {
console.error(e);
matAlert("Failed to update admin settings!");
}
}
render() {
return (
<SettingsSection title="General settings">
<TextField
required
label="Name"
value={this.state.newName}
onChange={this.changedName}
style={{ width: "100%", paddingBottom: "20px" }}
/>
<TextField
required
label="Email"
value={this.state.newEmail}
onChange={this.changedEmail}
type="mail"
style={{ width: "100%", paddingBottom: "20px" }}
/>
<Button
style={{ alignSelf: "end", marginRight: "10px" }}
disabled={!this.isValid}
onClick={this.handleSubmit}
>
Update
</Button>
</SettingsSection>
);
}
}
function SettingsSection(p: { title: string; children?: React.ReactNode }) {
return (
<Grid item xs={6} spacing={2}>
<Paper>
<Typography variant="h5" style={{ padding: "10px 10px " }}>
General settings
</Typography>
<Divider />
<div
style={{
padding: "10px 10px",
display: "flex",
flexDirection: "column",
}}
>
{p.children}
</div>
</Paper>
</Grid>
);
}

View File

@ -155,20 +155,28 @@ export function MainRoute() {
<Menu></Menu> <Menu></Menu>
</Paper> </Paper>
<div style={{ flex: "1", padding: "50px" }}> <div style={{ flex: "1", padding: "50px", height: "100%" }}>
<Switch> <div
<Route exact path="/"> style={{
<HomeRoute></HomeRoute> height: "100%",
</Route> margin: "auto",
maxWidth: "1280px",
}}
>
<Switch>
<Route exact path="/">
<HomeRoute></HomeRoute>
</Route>
<Route path="/accounts/:id"> <Route path="/accounts/:id">
<AccountSettingsRoute></AccountSettingsRoute> <AccountSettingsRoute></AccountSettingsRoute>
</Route> </Route>
<Route path="*"> <Route path="*">
<NotFoundRoute></NotFoundRoute> <NotFoundRoute></NotFoundRoute>
</Route> </Route>
</Switch> </Switch>
</div>
</div> </div>
</div> </div>
</Router> </Router>

View File

@ -59,7 +59,7 @@ export class AsyncWidget extends React.Component<
} }
componentDidUpdate() { componentDidUpdate() {
if (this.state.key != this.props.key) { if (this.state.key !== this.props.key) {
this.setState({ key: this.props.key }); this.setState({ key: this.props.key });
this.reload(); this.reload();
} }

View File

@ -1,242 +1,324 @@
/** /**
* Application dialogs provider * Application dialogs provider
* *
* @author Pierre Hubert * @author Pierre Hubert
*/ */
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button, TextField } from "@material-ui/core"; import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
TextField,
Snackbar,
IconButton,
} from "@material-ui/core";
import { Close } from "@material-ui/icons";
import React from "react"; import React from "react";
let cache : ApplicationDialogsProvider; let cache: ApplicationDialogsProvider;
export interface TextInputOptions { export interface TextInputOptions {
title ?: string, title?: string;
message ?: string, message?: string;
initialValue ?: string, initialValue?: string;
minLength ?: number, minLength?: number;
maxLength ?: number, maxLength?: number;
label : string, label: string;
} }
interface AppDiagProvState { interface AppDiagProvState {
// Alert dialog
alertMessage: string;
showAlert: boolean;
// Alert dialog // Simple snackbar
alertMessage : string, snackBarMessage: string;
showAlert : boolean, showSnackBar: boolean;
// Confirm dialog // Confirm dialog
confirmMessage: string, confirmMessage: string;
showConfirm: boolean, showConfirm: boolean;
resolveConfirm ?: (confirmed: boolean) => void, resolveConfirm?: (confirmed: boolean) => void;
// Text input dialog
showInputDialog: boolean,
inputValue: string,
inputOptions: TextInputOptions,
resolveInput ?: (text: string) => void,
// Text input dialog
showInputDialog: boolean;
inputValue: string;
inputOptions: TextInputOptions;
resolveInput?: (text: string) => void;
} }
export class ApplicationDialogsProvider extends React.Component<{}, AppDiagProvState> { export class ApplicationDialogsProvider extends React.Component<
private acceptConfirm: () => void; {},
private rejectConfirm: () => void; AppDiagProvState
private cancelInput: () => void; > {
private confirmInput: () => void; private acceptConfirm: () => void;
private rejectConfirm: () => void;
private cancelInput: () => void;
private confirmInput: () => void;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
// Alert dialog
alertMessage: "",
showAlert: false,
// Alert dialog // Simple snackbar
alertMessage: "", snackBarMessage: "",
showAlert: false, showSnackBar: false,
// Confirm dialog // Confirm dialog
showConfirm: false, showConfirm: false,
confirmMessage: "", confirmMessage: "",
resolveConfirm: undefined, resolveConfirm: undefined,
// Text input dialog // Text input dialog
showInputDialog: false, showInputDialog: false,
inputValue: "", inputValue: "",
inputOptions: { inputOptions: {
label: "" label: "",
}, },
resolveInput: undefined, resolveInput: undefined,
}; };
this.handleCloseAlert = this.handleCloseAlert.bind(this); this.handleCloseAlert = this.handleCloseAlert.bind(this);
this.acceptConfirm = this.handleCloseConfirm.bind(this, true); this.handleCloseSnackbar = this.handleCloseSnackbar.bind(this);
this.rejectConfirm = this.handleCloseConfirm.bind(this, false);
this.handleInputValueChanged = this.handleInputValueChanged.bind(this); this.acceptConfirm = this.handleCloseConfirm.bind(this, true);
this.cancelInput = this.handleCloseInput.bind(this, true); this.rejectConfirm = this.handleCloseConfirm.bind(this, false);
this.confirmInput = this.handleCloseInput.bind(this, false);
}
showAlert(message: string) { this.handleInputValueChanged = this.handleInputValueChanged.bind(this);
this.setState({ this.cancelInput = this.handleCloseInput.bind(this, true);
showAlert: true, this.confirmInput = this.handleCloseInput.bind(this, false);
alertMessage: message, }
})
}
handleCloseAlert() { showAlert(message: string) {
this.setState({showAlert: false}); this.setState({
} showAlert: true,
alertMessage: message,
});
}
showConfirm(message: string) : Promise<boolean> { handleCloseAlert() {
return new Promise((res, _rej) => { this.setState({ showAlert: false });
this.setState({ }
showConfirm: true,
confirmMessage: message,
resolveConfirm: res
});
});
}
handleCloseConfirm(accept: boolean) { showSnackbar(message: string) {
this.setState({ this.setState({ showSnackBar: true, snackBarMessage: message });
showConfirm: false }
});
this.state.resolveConfirm && this.state.resolveConfirm(accept); handleCloseSnackbar(
} _event: React.SyntheticEvent | React.MouseEvent,
reason?: string
) {
if (reason === "clickaway") {
return;
}
showInput(options: TextInputOptions) : Promise<string> { this.setState({ showSnackBar: false });
return new Promise((res, _rej) => { }
this.setState({
showInputDialog: true,
inputOptions: options,
resolveInput: res,
inputValue: options.initialValue || ""
});
});
}
handleInputValueChanged(e: React.ChangeEvent<HTMLInputElement>){ showConfirm(message: string): Promise<boolean> {
this.setState({inputValue: e.target.value}); return new Promise((res, _rej) => {
} this.setState({
showConfirm: true,
confirmMessage: message,
resolveConfirm: res,
});
});
}
handleCloseInput(cancel: boolean) { handleCloseConfirm(accept: boolean) {
this.setState({ this.setState({
showInputDialog: false showConfirm: false,
}); });
if (!cancel) this.state.resolveConfirm && this.state.resolveConfirm(accept);
this.state.resolveInput && this.state.resolveInput(this.state.inputValue); }
}
get isInputValid() : boolean {
const options = this.state.inputOptions;
const value = this.state.inputValue;
if (options.minLength && value.length < options.minLength) showInput(options: TextInputOptions): Promise<string> {
return false; return new Promise((res, _rej) => {
this.setState({
showInputDialog: true,
inputOptions: options,
resolveInput: res,
inputValue: options.initialValue || "",
});
});
}
if (options.maxLength && value.length > options.maxLength) handleInputValueChanged(e: React.ChangeEvent<HTMLInputElement>) {
return false; this.setState({ inputValue: e.target.value });
}
return true; handleCloseInput(cancel: boolean) {
} this.setState({
showInputDialog: false,
});
render() { if (!cancel)
cache = this; this.state.resolveInput &&
this.state.resolveInput(this.state.inputValue);
}
if(this.state == null) get isInputValid(): boolean {
return(<div></div>); const options = this.state.inputOptions;
const value = this.state.inputValue;
return (<div> if (options.minLength && value.length < options.minLength) return false;
{/* Simple alert dialog */}
<Dialog
open={this.state.showAlert}
onClose={this.handleCloseAlert}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title" style={{minWidth: "300px"}}>Message</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{this.state.alertMessage}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleCloseAlert} color="default">
OK
</Button>
</DialogActions>
</Dialog>
{/* Confirm dialog */} if (options.maxLength && value.length > options.maxLength) return false;
<Dialog
open={this.state.showConfirm}
onClose={this.rejectConfirm}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title" style={{minWidth: "300px"}}>Confirm action</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{this.state.confirmMessage}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.rejectConfirm} color="default">
Cancel
</Button>
<Button onClick={this.acceptConfirm} color="default">
Confirm
</Button>
</DialogActions>
</Dialog>
{/* Text input dialog */} return true;
<Dialog }
open={this.state.showInputDialog}
onClose={this.rejectConfirm}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{this.state.inputOptions.title || this.state.inputOptions.label}
</DialogTitle>
<DialogContent>
{this.state.inputOptions.message ? <DialogContentText id="alert-dialog-description">
{this.state.inputOptions.message} <br /><br />
</DialogContentText> : <span></span> }
<TextField render() {
label={this.state.inputOptions.label} cache = this;
variant="outlined"
value={this.state.inputValue} if (this.state == null) return <div></div>;
onChange={this.handleInputValueChanged}
return (
/> <div>
</DialogContent> {/* Simple alert dialog */}
<DialogActions> <Dialog
<Button onClick={this.cancelInput} color="default"> open={this.state.showAlert}
Cancel onClose={this.handleCloseAlert}
</Button> aria-labelledby="alert-dialog-title"
<Button onClick={this.confirmInput} color="default" disabled={!this.isInputValid}> aria-describedby="alert-dialog-description"
OK >
</Button> <DialogTitle
</DialogActions> id="alert-dialog-title"
</Dialog> style={{ minWidth: "300px" }}
</div>) >
} Message
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{this.state.alertMessage}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleCloseAlert} color="default">
OK
</Button>
</DialogActions>
</Dialog>
{/* Simple snackbar */}
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
open={this.state.showSnackBar}
autoHideDuration={6000}
onClose={this.handleCloseSnackbar}
message={this.state.snackBarMessage}
action={
<React.Fragment>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={this.handleCloseSnackbar}
>
<Close fontSize="small" />
</IconButton>
</React.Fragment>
}
/>
{/* Confirm dialog */}
<Dialog
open={this.state.showConfirm}
onClose={this.rejectConfirm}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle
id="alert-dialog-title"
style={{ minWidth: "300px" }}
>
Confirm action
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{this.state.confirmMessage}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.rejectConfirm} color="default">
Cancel
</Button>
<Button onClick={this.acceptConfirm} color="default">
Confirm
</Button>
</DialogActions>
</Dialog>
{/* Text input dialog */}
<Dialog
open={this.state.showInputDialog}
onClose={this.rejectConfirm}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{this.state.inputOptions.title ||
this.state.inputOptions.label}
</DialogTitle>
<DialogContent>
{this.state.inputOptions.message ? (
<DialogContentText id="alert-dialog-description">
{this.state.inputOptions.message} <br />
<br />
</DialogContentText>
) : (
<span></span>
)}
<TextField
label={this.state.inputOptions.label}
variant="outlined"
value={this.state.inputValue}
onChange={this.handleInputValueChanged}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.cancelInput} color="default">
Cancel
</Button>
<Button
onClick={this.confirmInput}
color="default"
disabled={!this.isInputValid}
>
OK
</Button>
</DialogActions>
</Dialog>
</div>
);
}
} }
export function matAlert(msg: string) { export function matAlert(msg: string) {
cache.showAlert(msg); cache.showAlert(msg);
} }
export function matConfirm(msg: string) : Promise<boolean> { export function snackbar(msg: string) {
return cache.showConfirm(msg); cache.showSnackbar(msg);
} }
export function input(options: TextInputOptions) : Promise<string> { export function matConfirm(msg: string): Promise<boolean> {
return cache.showInput(options); return cache.showConfirm(msg);
} }
export function input(options: TextInputOptions): Promise<string> {
return cache.showInput(options);
}