From d9add8605c567f4139ef20b01ffbe5e812067530 Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Mon, 15 Feb 2021 15:42:10 -0800 Subject: [PATCH] Complete account change --- packages/webapp/src/classes/client/index.ts | 5 +- .../src/classes/client/mock-client/index.ts | 7 +- .../src/classes/client/rest-client/index.ts | 32 +++++- .../src/components/form/input/index.tsx | 28 +++-- .../account-info-dialog/index.tsx | 94 +++++++++++++++ .../change-password-dialog/index.tsx | 84 ++++++++++++++ .../maps-page/account-menu/index.tsx | 107 +++--------------- 7 files changed, 245 insertions(+), 112 deletions(-) create mode 100644 packages/webapp/src/components/maps-page/account-menu/account-info-dialog/index.tsx create mode 100644 packages/webapp/src/components/maps-page/account-menu/change-password-dialog/index.tsx diff --git a/packages/webapp/src/classes/client/index.ts b/packages/webapp/src/classes/client/index.ts index 7f64cf20..bd82380d 100644 --- a/packages/webapp/src/classes/client/index.ts +++ b/packages/webapp/src/classes/client/index.ts @@ -58,8 +58,8 @@ export type ErrorInfo = { } export type AccountInfo = { - firstName: string; - lastName: string; + firstname: string; + lastname: string; email: string; locale: Locale; } @@ -75,6 +75,7 @@ interface Client { updateAccountLanguage(locale: LocaleCode): Promise; updateAccountPassword(pasword: string): Promise; + updateAccountInfo(firstname: string,lastname: string): Promise; updateStarred(id: number, starred: boolean): Promise; updateMapToPublic(id: number, starred: boolean): Promise; diff --git a/packages/webapp/src/classes/client/mock-client/index.ts b/packages/webapp/src/classes/client/mock-client/index.ts index cd773b46..45b624f4 100644 --- a/packages/webapp/src/classes/client/mock-client/index.ts +++ b/packages/webapp/src/classes/client/mock-client/index.ts @@ -34,6 +34,9 @@ class MockClient implements Client { ]; } + updateAccountInfo(firstname: string, lastname: string): Promise { + throw new Error('Method not implemented.'); + } updateAccountPassword(pasword: string): Promise { return Promise.resolve(); @@ -52,8 +55,8 @@ class MockClient implements Client { console.log('Fetch account info ...') const locale: LocaleCode | null = localStorage.getItem('locale') as LocaleCode; return Promise.resolve({ - firstName: 'Costme', - lastName: 'Fulanito', + firstname: 'Costme', + lastname: 'Fulanito', email: 'test@example.com', locale: localeFromStr(locale) }); diff --git a/packages/webapp/src/classes/client/rest-client/index.ts b/packages/webapp/src/classes/client/rest-client/index.ts index f41d823c..85f70663 100644 --- a/packages/webapp/src/classes/client/rest-client/index.ts +++ b/packages/webapp/src/classes/client/rest-client/index.ts @@ -10,11 +10,32 @@ export default class RestClient implements Client { this.baseUrl = baseUrl; this.sessionExpired = sessionExpired; } - + + updateAccountInfo(firstname: string, lastname: string): Promise { + const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { + axios.put(`${this.baseUrl}/c/restful/account/firstname`, + firstname, + { headers: { 'Content-Type': 'text/plain' } } + ).then(() => { + return axios.put(`${this.baseUrl}/c/restful/account/lastname`, + lastname, + { headers: { 'Content-Type': 'text/plain' } } + ) + }).then(() => { + // All was ok, let's sent to success page ...; + success(); + }).catch(error => { + const errorInfo = this.parseResponseOnError(error.response); + reject(errorInfo); + }); + } + return new Promise(handler); + } + updateAccountPassword(pasword: string): Promise { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { axios.put(`${this.baseUrl}/c/restful/account/password`, - pasword, + pasword, { headers: { 'Content-Type': 'text/plain' } } ).then(() => { success(); @@ -23,7 +44,8 @@ export default class RestClient implements Client { reject(errorInfo); }); } - return new Promise(handler); } + return new Promise(handler); + } updateAccountLanguage(locale: LocaleCode): Promise { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { @@ -68,8 +90,8 @@ export default class RestClient implements Client { const account = response.data; const locale: LocaleCode | null = account.locale; success({ - lastName: account.lastName ? account.lastName : '', - firstName: account.fistName ? account.fistName : '', + lastname: account.lastname ? account.lastname : '', + firstname: account.firstname ? account.firstname : '', email: account.email, locale: locale ? localeFromStr(locale) : Locales.EN }); diff --git a/packages/webapp/src/components/form/input/index.tsx b/packages/webapp/src/components/form/input/index.tsx index 811c3e31..a162a54f 100644 --- a/packages/webapp/src/components/form/input/index.tsx +++ b/packages/webapp/src/components/form/input/index.tsx @@ -1,6 +1,5 @@ import { TextField } from "@material-ui/core"; import React, { ChangeEvent } from "react"; -import { MessageDescriptor, useIntl } from "react-intl"; import { ErrorInfo } from "../../../classes/client"; type InputProps = { @@ -13,24 +12,29 @@ type InputProps = { value?: string autoComplete?: string; fullWidth?: boolean + disabled?: boolean } -const Input = (props: InputProps) => { +const Input = ({ + name, + error, + onChange, + required = true, + type, + value, + label, + autoComplete, + fullWidth = true, + disabled = false + +}: InputProps) => { - const intl = useIntl(); - const error: ErrorInfo | undefined = props?.error; - const name = props.name; - const value = props.value; - const onChange = props.onChange ? props.onChange : () => { }; const fieldError = error?.fields?.[name]; - const required = props.required != undefined ? props.required : true; - const fullWidth = props.fullWidth != undefined ? props.required : true; - return ( - + variant="outlined" required={required} fullWidth={fullWidth} margin="dense" disabled={disabled} autoComplete={autoComplete} /> ); } diff --git a/packages/webapp/src/components/maps-page/account-menu/account-info-dialog/index.tsx b/packages/webapp/src/components/maps-page/account-menu/account-info-dialog/index.tsx new file mode 100644 index 00000000..307eef70 --- /dev/null +++ b/packages/webapp/src/components/maps-page/account-menu/account-info-dialog/index.tsx @@ -0,0 +1,94 @@ +import { FormControl } from "@material-ui/core"; +import React, { useEffect } from "react"; +import { useIntl } from "react-intl"; +import { useMutation, useQueryClient } from "react-query"; +import Client, { ErrorInfo } from "../../../../classes/client"; +import Input from "../../../form/input"; +import BaseDialog from "../../action-dispatcher/base-dialog"; +import { useSelector } from 'react-redux'; +import { activeInstance, fetchAccount } from "../../../../redux/clientSlice"; + + +type AccountInfoDialogProps = { + onClose: () => void +} + +type AccountInfoModel = { + email: string, + firstname: string, + lastname: string +} + +const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' }; +const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps) => { + const client: Client = useSelector(activeInstance); + const queryClient = useQueryClient(); + + const [model, setModel] = React.useState(defaultModel); + const [error, setError] = React.useState(); + const intl = useIntl(); + + const mutation = useMutation((model: AccountInfoModel) => { + return client.updateAccountInfo(model.firstname, model.lastname); + }, + { + onSuccess: () => { + queryClient.invalidateQueries('account') + onClose() + }, + onError: (error) => { + setError(error); + } + } + ); + + const account = fetchAccount(); + useEffect(() => { + if (account) { + setModel({ + email: account?.email, + lastname: account?.lastname, + firstname: account?.firstname + }); + } + }, [account?.email]) + + + const handleOnClose = (): void => { + onClose(); + setModel(defaultModel); + setError(undefined); + }; + + const handleOnSubmit = (event: React.FormEvent): void => { + event.preventDefault(); + mutation.mutate(model); + }; + + const handleOnChange = (event: React.ChangeEvent): void => { + event.preventDefault(); + + const name = event.target.name; + const value = event.target.value; + setModel({ ...model, [name as keyof AccountInfoModel]: value }); + } + return ( + + + + + + + + + + + ); +} +export default AccountInfoDialog; + diff --git a/packages/webapp/src/components/maps-page/account-menu/change-password-dialog/index.tsx b/packages/webapp/src/components/maps-page/account-menu/change-password-dialog/index.tsx new file mode 100644 index 00000000..debd73bc --- /dev/null +++ b/packages/webapp/src/components/maps-page/account-menu/change-password-dialog/index.tsx @@ -0,0 +1,84 @@ +import { FormControl } from "@material-ui/core"; +import React from "react"; +import { useIntl } from "react-intl"; +import { useMutation } from "react-query"; +import Client, { ErrorInfo } from "../../../../classes/client"; +import Input from "../../../form/input"; +import BaseDialog from "../../action-dispatcher/base-dialog"; +import { useSelector } from 'react-redux'; +import { activeInstance } from "../../../../redux/clientSlice"; + + +type ChangePasswordDialogProps = { + onClose: () => void +} + +type ChangePasswordModel = { + password: string, + retryPassword: string +} + +const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' }; +const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps) => { + const client: Client = useSelector(activeInstance); + const [model, setModel] = React.useState(defaultModel); + const [error, setError] = React.useState(); + const intl = useIntl(); + + const mutation = useMutation((model: ChangePasswordModel) => { + return client.updateAccountPassword(model.password); + }, + { + onSuccess: () => { + onClose() + }, + onError: (error) => { + setError(error); + } + } + ); + + const handleOnClose = (): void => { + onClose(); + setModel(defaultModel); + setError(undefined); + }; + + const handleOnSubmit = (event: React.FormEvent): void => { + event.preventDefault(); + + // Check password are equal ... + if (model.password != model.retryPassword) { + setError({ msg: intl.formatMessage({ id: 'changepwd.password-match', defaultMessage: 'Password do not match. Please, try again.' }) }); + return; + } + + mutation.mutate(model); + }; + + const handleOnChange = (event: React.ChangeEvent): void => { + event.preventDefault(); + + const name = event.target.name; + const value = event.target.value; + setModel({ ...model, [name as keyof ChangePasswordModel]: value }); + } + + return ( + + + + + + + + + ); +} +export default ChangePasswordDialog; + diff --git a/packages/webapp/src/components/maps-page/account-menu/index.tsx b/packages/webapp/src/components/maps-page/account-menu/index.tsx index 77c2bf4e..5ccef486 100644 --- a/packages/webapp/src/components/maps-page/account-menu/index.tsx +++ b/packages/webapp/src/components/maps-page/account-menu/index.tsx @@ -1,19 +1,16 @@ -import { FormControl, IconButton, Link, ListItemIcon, Menu, MenuItem, Tooltip } from '@material-ui/core'; +import { IconButton, Link, ListItemIcon, Menu, MenuItem, Tooltip } from '@material-ui/core'; import { AccountCircle, ExitToAppOutlined, LockOpenOutlined, SettingsApplicationsOutlined } from '@material-ui/icons'; import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; -import { useMutation } from "react-query"; -import Client, { ErrorInfo } from "../../../classes/client"; -import { useSelector } from 'react-redux'; -import { activeInstance, fetchAccount } from '../../../redux/clientSlice'; -import BaseDialog from '../action-dispatcher/base-dialog'; -import Input from '../../form/input'; - +import { FormattedMessage } from "react-intl"; +import { fetchAccount } from '../../../redux/clientSlice'; +import AccountInfoDialog from './account-info-dialog'; +import ChangePasswordDialog from './change-password-dialog'; +type ActionType = 'change-password' | 'account-info' | undefined; const AccountMenu = () => { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); - const [openRenameDialog, setOpenRenameDialog] = React.useState(false); + const [action, setAction] = React.useState(undefined); const handleMenu = (event: React.MouseEvent) => { @@ -27,7 +24,7 @@ const AccountMenu = () => { const account = fetchAccount(); return ( - `}> + `}> @@ -48,18 +45,18 @@ const AccountMenu = () => { horizontal: 'right', }} > - + { handleClose(), setAction('account-info') }}> - { handleClose(), setOpenRenameDialog(true) }}> + { handleClose(), setAction('change-password') }}> - + @@ -71,87 +68,15 @@ const AccountMenu = () => { - {openRenameDialog && - setOpenRenameDialog(false)} /> + {action == 'change-password' && + setAction(undefined)} /> + } + {action == 'account-info' && + setAction(undefined)} /> } ); } -type ChangePasswordDialogProps = { - onClose: () => void -} - -type ChangePasswordModel = { - password: string, - retryPassword: string -} - -const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' }; -const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps) => { - const client: Client = useSelector(activeInstance); - const [model, setModel] = React.useState(defaultModel); - const [error, setError] = React.useState(); - const intl = useIntl(); - - const mutation = useMutation((model: ChangePasswordModel) => { - return client.updateAccountPassword(model.password); - }, - { - onSuccess: () => { - onClose() - }, - onError: (error) => { - setError(error); - } - } - ); - - const handleOnClose = (): void => { - onClose(); - setModel(defaultModel); - setError(undefined); - }; - - const handleOnSubmit = (event: React.FormEvent): void => { - event.preventDefault(); - - // Check password are equal ... - if (model.password != model.retryPassword) { - setError({ msg: intl.formatMessage({ id: 'changepwd.password-match', defaultMessage: 'Password do not match. Please, try again.' }) }); - return; - } - - mutation.mutate(model); - }; - - const handleOnChange = (event: React.ChangeEvent): void => { - event.preventDefault(); - - const name = event.target.name; - const value = event.target.value; - setModel({ ...model, [name as keyof ChangePasswordModel]: value }); - } - - return ( -
- - - - - - - - -
- ); -} - - export default AccountMenu; \ No newline at end of file