From 6dde0f40c4a6f963a5cc362c7dff9b8b5d7a6cc3 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 13 May 2021 16:58:50 +0200 Subject: [PATCH] Can request admin settings update --- src/helpers/AccountHelper.ts | 19 + src/ui/routes/AccountSettingsRoute.tsx | 115 +++++- src/ui/routes/MainRoute.tsx | 32 +- src/ui/widgets/AsyncWidget.tsx | 2 +- src/ui/widgets/DialogsProvider.tsx | 466 +++++++++++++++---------- 5 files changed, 428 insertions(+), 206 deletions(-) diff --git a/src/helpers/AccountHelper.ts b/src/helpers/AccountHelper.ts index 3947137..567a012 100644 --- a/src/helpers/AccountHelper.ts +++ b/src/helpers/AccountHelper.ts @@ -17,6 +17,12 @@ export interface AdminAccount { time_create: number; } +export interface NewAdminGeneralSettings { + id: number; + name: string; + email: string; +} + const SESSION_STORAGE_TOKEN = "auth_token"; let currentAccount: AdminAccount | null = null; @@ -114,4 +120,17 @@ export class AccountHelper { sessionStorage.removeItem(SESSION_STORAGE_TOKEN); 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, + }); + } } diff --git a/src/ui/routes/AccountSettingsRoute.tsx b/src/ui/routes/AccountSettingsRoute.tsx index 8b5d4d8..43adf86 100644 --- a/src/ui/routes/AccountSettingsRoute.tsx +++ b/src/ui/routes/AccountSettingsRoute.tsx @@ -4,10 +4,19 @@ * @author Pierre Hubert */ +import { + Button, + Divider, + Grid, + Paper, + TextField, + Typography, +} from "@material-ui/core"; import React from "react"; import { useParams } from "react-router-dom"; import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper"; import { AsyncWidget } from "../widgets/AsyncWidget"; +import { matAlert, snackbar } from "../widgets/DialogsProvider"; export function AccountSettingsRoute() { let params: any = useParams(); @@ -52,6 +61,110 @@ class AccountSettingsRouteInner extends React.Component< } build() { - return

{this.state.account.email}

; + return ( + + + + ); } } + +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) { + this.setState({ newName: e.target.value }); + } + + changedEmail(e: React.ChangeEvent) { + 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 ( + + + + + + + + ); + } +} + +function SettingsSection(p: { title: string; children?: React.ReactNode }) { + return ( + + + + General settings + + +
+ {p.children} +
+
+
+ ); +} diff --git a/src/ui/routes/MainRoute.tsx b/src/ui/routes/MainRoute.tsx index 5cab5ac..ead154c 100644 --- a/src/ui/routes/MainRoute.tsx +++ b/src/ui/routes/MainRoute.tsx @@ -155,20 +155,28 @@ export function MainRoute() { -
- - - - +
+
+ + + + - - - + + + - - - - + + + + +
diff --git a/src/ui/widgets/AsyncWidget.tsx b/src/ui/widgets/AsyncWidget.tsx index 9a6f251..9591370 100644 --- a/src/ui/widgets/AsyncWidget.tsx +++ b/src/ui/widgets/AsyncWidget.tsx @@ -59,7 +59,7 @@ export class AsyncWidget extends React.Component< } componentDidUpdate() { - if (this.state.key != this.props.key) { + if (this.state.key !== this.props.key) { this.setState({ key: this.props.key }); this.reload(); } diff --git a/src/ui/widgets/DialogsProvider.tsx b/src/ui/widgets/DialogsProvider.tsx index 188d5f8..a4231c8 100644 --- a/src/ui/widgets/DialogsProvider.tsx +++ b/src/ui/widgets/DialogsProvider.tsx @@ -1,242 +1,324 @@ /** * Application dialogs provider - * + * * @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"; -let cache : ApplicationDialogsProvider; +let cache: ApplicationDialogsProvider; export interface TextInputOptions { - title ?: string, - message ?: string, - initialValue ?: string, - minLength ?: number, - maxLength ?: number, - label : string, + title?: string; + message?: string; + initialValue?: string; + minLength?: number; + maxLength?: number; + label: string; } interface AppDiagProvState { + // Alert dialog + alertMessage: string; + showAlert: boolean; - // Alert dialog - alertMessage : string, - showAlert : boolean, + // Simple snackbar + snackBarMessage: string; + showSnackBar: boolean; - // Confirm dialog - confirmMessage: string, - showConfirm: boolean, - resolveConfirm ?: (confirmed: boolean) => void, - - // Text input dialog - showInputDialog: boolean, - inputValue: string, - inputOptions: TextInputOptions, - resolveInput ?: (text: string) => void, + // Confirm dialog + confirmMessage: string; + showConfirm: boolean; + resolveConfirm?: (confirmed: boolean) => void; + // Text input dialog + showInputDialog: boolean; + inputValue: string; + inputOptions: TextInputOptions; + resolveInput?: (text: string) => void; } -export class ApplicationDialogsProvider extends React.Component<{}, AppDiagProvState> { - private acceptConfirm: () => void; - private rejectConfirm: () => void; - private cancelInput: () => void; - private confirmInput: () => void; +export class ApplicationDialogsProvider extends React.Component< + {}, + AppDiagProvState +> { + private acceptConfirm: () => void; + private rejectConfirm: () => void; + private cancelInput: () => void; + private confirmInput: () => void; - constructor(props: any) { - super(props); + constructor(props: any) { + super(props); - this.state = { + this.state = { + // Alert dialog + alertMessage: "", + showAlert: false, - // Alert dialog - alertMessage: "", - showAlert: false, + // Simple snackbar + snackBarMessage: "", + showSnackBar: false, - // Confirm dialog - showConfirm: false, - confirmMessage: "", - resolveConfirm: undefined, + // Confirm dialog + showConfirm: false, + confirmMessage: "", + resolveConfirm: undefined, - // Text input dialog - showInputDialog: false, - inputValue: "", - inputOptions: { - label: "" - }, - resolveInput: undefined, - }; + // Text input dialog + showInputDialog: false, + inputValue: "", + inputOptions: { + label: "", + }, + resolveInput: undefined, + }; - this.handleCloseAlert = this.handleCloseAlert.bind(this); + this.handleCloseAlert = this.handleCloseAlert.bind(this); - this.acceptConfirm = this.handleCloseConfirm.bind(this, true); - this.rejectConfirm = this.handleCloseConfirm.bind(this, false); + this.handleCloseSnackbar = this.handleCloseSnackbar.bind(this); - this.handleInputValueChanged = this.handleInputValueChanged.bind(this); - this.cancelInput = this.handleCloseInput.bind(this, true); - this.confirmInput = this.handleCloseInput.bind(this, false); - } + this.acceptConfirm = this.handleCloseConfirm.bind(this, true); + this.rejectConfirm = this.handleCloseConfirm.bind(this, false); - showAlert(message: string) { - this.setState({ - showAlert: true, - alertMessage: message, - }) - } + this.handleInputValueChanged = this.handleInputValueChanged.bind(this); + this.cancelInput = this.handleCloseInput.bind(this, true); + this.confirmInput = this.handleCloseInput.bind(this, false); + } - handleCloseAlert() { - this.setState({showAlert: false}); - } + showAlert(message: string) { + this.setState({ + showAlert: true, + alertMessage: message, + }); + } - showConfirm(message: string) : Promise { - return new Promise((res, _rej) => { - this.setState({ - showConfirm: true, - confirmMessage: message, - resolveConfirm: res - }); - }); - } + handleCloseAlert() { + this.setState({ showAlert: false }); + } - handleCloseConfirm(accept: boolean) { - this.setState({ - showConfirm: false - }); + showSnackbar(message: string) { + this.setState({ showSnackBar: true, snackBarMessage: message }); + } - this.state.resolveConfirm && this.state.resolveConfirm(accept); - } + handleCloseSnackbar( + _event: React.SyntheticEvent | React.MouseEvent, + reason?: string + ) { + if (reason === "clickaway") { + return; + } - showInput(options: TextInputOptions) : Promise { - return new Promise((res, _rej) => { - this.setState({ - showInputDialog: true, - inputOptions: options, - resolveInput: res, - inputValue: options.initialValue || "" - }); - }); - } + this.setState({ showSnackBar: false }); + } - handleInputValueChanged(e: React.ChangeEvent){ - this.setState({inputValue: e.target.value}); - } + showConfirm(message: string): Promise { + return new Promise((res, _rej) => { + this.setState({ + showConfirm: true, + confirmMessage: message, + resolveConfirm: res, + }); + }); + } - handleCloseInput(cancel: boolean) { - this.setState({ - showInputDialog: false - }); + handleCloseConfirm(accept: boolean) { + this.setState({ + showConfirm: false, + }); - if (!cancel) - this.state.resolveInput && this.state.resolveInput(this.state.inputValue); - } - - get isInputValid() : boolean { - const options = this.state.inputOptions; - const value = this.state.inputValue; + this.state.resolveConfirm && this.state.resolveConfirm(accept); + } - if (options.minLength && value.length < options.minLength) - return false; + showInput(options: TextInputOptions): Promise { + return new Promise((res, _rej) => { + this.setState({ + showInputDialog: true, + inputOptions: options, + resolveInput: res, + inputValue: options.initialValue || "", + }); + }); + } - if (options.maxLength && value.length > options.maxLength) - return false; + handleInputValueChanged(e: React.ChangeEvent) { + this.setState({ inputValue: e.target.value }); + } - return true; - } + handleCloseInput(cancel: boolean) { + this.setState({ + showInputDialog: false, + }); - render() { - cache = this; + if (!cancel) + this.state.resolveInput && + this.state.resolveInput(this.state.inputValue); + } - if(this.state == null) - return(
); + get isInputValid(): boolean { + const options = this.state.inputOptions; + const value = this.state.inputValue; - return (
- - {/* Simple alert dialog */} - - Message - - - {this.state.alertMessage} - - - - - - + if (options.minLength && value.length < options.minLength) return false; - {/* Confirm dialog */} - - Confirm action - - - {this.state.confirmMessage} - - - - - - - + if (options.maxLength && value.length > options.maxLength) return false; - {/* Text input dialog */} - - - {this.state.inputOptions.title || this.state.inputOptions.label} - - - {this.state.inputOptions.message ? - {this.state.inputOptions.message}

-
: } + return true; + } - -
- - - - -
-
) - } + render() { + cache = this; + + if (this.state == null) return
; + + return ( +
+ {/* Simple alert dialog */} + + + Message + + + + {this.state.alertMessage} + + + + + + + + {/* Simple snackbar */} + + + + + + } + /> + + {/* Confirm dialog */} + + + Confirm action + + + + {this.state.confirmMessage} + + + + + + + + + {/* Text input dialog */} + + + {this.state.inputOptions.title || + this.state.inputOptions.label} + + + {this.state.inputOptions.message ? ( + + {this.state.inputOptions.message}
+
+
+ ) : ( + + )} + + +
+ + + + +
+
+ ); + } } export function matAlert(msg: string) { - cache.showAlert(msg); + cache.showAlert(msg); } -export function matConfirm(msg: string) : Promise { - return cache.showConfirm(msg); +export function snackbar(msg: string) { + cache.showSnackbar(msg); } -export function input(options: TextInputOptions) : Promise { - return cache.showInput(options); -} \ No newline at end of file +export function matConfirm(msg: string): Promise { + return cache.showConfirm(msg); +} + +export function input(options: TextInputOptions): Promise { + return cache.showInput(options); +}