Webatrice: Add account validation dialog/form (#4547)
* Add account validation dialog/form * clean up * close registration dialog on token request * remove dupe code * add subtitle styling Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
This commit is contained in:
parent
513fcb0908
commit
1d780058c8
12 changed files with 200 additions and 16 deletions
|
@ -9,7 +9,7 @@ import Typography from '@material-ui/core/Typography';
|
|||
|
||||
|
||||
import { AuthenticationService } from 'api';
|
||||
import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog } from 'dialogs';
|
||||
import { RegistrationDialog, RequestPasswordResetDialog, ResetPasswordDialog, AccountActivationDialog } from 'dialogs';
|
||||
import { LoginForm } from 'forms';
|
||||
import { useReduxEffect, useFireOnce } from 'hooks';
|
||||
import { Images } from 'images';
|
||||
|
@ -65,6 +65,7 @@ const Login = ({ state, description }: LoginProps) => {
|
|||
passwordResetRequestDialog: false,
|
||||
resetPasswordDialog: false,
|
||||
registrationDialog: false,
|
||||
activationDialog: false,
|
||||
});
|
||||
const [userToResetPassword, setUserToResetPassword] = useState(null);
|
||||
|
||||
|
@ -77,6 +78,24 @@ const Login = ({ state, description }: LoginProps) => {
|
|||
closeResetPasswordDialog();
|
||||
}, ServerTypes.RESET_PASSWORD_SUCCESS, []);
|
||||
|
||||
useReduxEffect(() => {
|
||||
closeActivateAccountDialog();
|
||||
}, ServerTypes.ACCOUNT_ACTIVATION_SUCCESS, []);
|
||||
|
||||
useReduxEffect(() => {
|
||||
closeRegistrationDialog();
|
||||
openActivateAccountDialog();
|
||||
}, ServerTypes.ACCOUNT_AWAITING_ACTIVATION, []);
|
||||
|
||||
useReduxEffect(({ options: { hashedPassword } }) => {
|
||||
if (hostIdToRemember) {
|
||||
HostDTO.get(hostIdToRemember).then(host => {
|
||||
host.hashedPassword = hashedPassword;
|
||||
host.save();
|
||||
});
|
||||
}
|
||||
}, ServerTypes.LOGIN_SUCCESSFUL, [hostIdToRemember]);
|
||||
|
||||
const showDescription = () => {
|
||||
return !isConnected && description?.length;
|
||||
};
|
||||
|
@ -112,18 +131,7 @@ const Login = ({ state, description }: LoginProps) => {
|
|||
AuthenticationService.login(options as WebSocketConnectOptions);
|
||||
}, []);
|
||||
|
||||
const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin)
|
||||
|
||||
useReduxEffect(({ options: { hashedPassword } }) => {
|
||||
if (hostIdToRemember) {
|
||||
HostDTO.get(hostIdToRemember).then(host => {
|
||||
host.hashedPassword = hashedPassword;
|
||||
host.save();
|
||||
});
|
||||
}
|
||||
resetSubmitButton()
|
||||
}, ServerTypes.LOGIN_SUCCESSFUL, [hostIdToRemember]);
|
||||
|
||||
const [submitButtonDisabled, resetSubmitButton, handleLogin] = useFireOnce(onSubmitLogin);
|
||||
|
||||
const updateHost = ({ selectedHost, userName, hashedPassword, remember }) => {
|
||||
HostDTO.get(selectedHost.id).then(hostDTO => {
|
||||
|
@ -165,6 +173,10 @@ const Login = ({ state, description }: LoginProps) => {
|
|||
AuthenticationService.resetPassword({ userName, token, newPassword, host, port } as any);
|
||||
};
|
||||
|
||||
const handleAccountActivationDialogSubmit = ({ token }) => {
|
||||
AuthenticationService.activateAccount({ token } as any);
|
||||
};
|
||||
|
||||
const skipTokenRequest = (userName) => {
|
||||
setUserToResetPassword(userName);
|
||||
|
||||
|
@ -198,6 +210,14 @@ const Login = ({ state, description }: LoginProps) => {
|
|||
setDialogState(s => ({ ...s, registrationDialog: true }));
|
||||
}
|
||||
|
||||
const closeActivateAccountDialog = () => {
|
||||
setDialogState(s => ({ ...s, activationDialog: false }));
|
||||
};
|
||||
|
||||
const openActivateAccountDialog = () => {
|
||||
setDialogState(s => ({ ...s, activationDialog: true }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'login overflow-scroll ' + classes.root}>
|
||||
{ isConnected && <Redirect from="*" to={RouteEnum.SERVER} />}
|
||||
|
@ -296,6 +316,12 @@ const Login = ({ state, description }: LoginProps) => {
|
|||
handleClose={closeResetPasswordDialog}
|
||||
userName={userToResetPassword}
|
||||
/>
|
||||
|
||||
<AccountActivationDialog
|
||||
isOpen={dialogState.activationDialog}
|
||||
onSubmit={handleAccountActivationDialogSubmit}
|
||||
handleClose={closeActivateAccountDialog}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
.dialog-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.MuiDialogTitle-root.dialog-title {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-bottom: 20px;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import { AccountActivationForm } from 'forms';
|
||||
|
||||
import './AccountActivationDialog.css';
|
||||
|
||||
const AccountActivationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => {
|
||||
const handleOnClose = () => {
|
||||
handleClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={handleOnClose} open={isOpen}>
|
||||
<DialogTitle disableTypography className="dialog-title">
|
||||
<Typography variant="h6">Account Activation</Typography>
|
||||
|
||||
{handleOnClose ? (
|
||||
<IconButton onClick={handleOnClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<div className="content">
|
||||
<Typography variant='subtitle1'>Your account has not been activated yet.</Typography>
|
||||
<Typography variant='subtitle1'>You need to provide the activation token received in the activation email.</Typography>
|
||||
</div>
|
||||
|
||||
<AccountActivationForm onSubmit={onSubmit}></AccountActivationForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountActivationDialog;
|
|
@ -1,3 +1,4 @@
|
|||
export { default as AccountActivationDialog } from './AccountActivationDialog/AccountActivationDialog';
|
||||
export { default as CardImportDialog } from './CardImportDialog/CardImportDialog';
|
||||
export { default as KnownHostDialog } from './KnownHostDialog/KnownHostDialog';
|
||||
export { default as RegistrationDialog } from './RegistrationDialog/RegistrationDialog';
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.AccountActivationForm {
|
||||
width: 100%;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.AccountActivationForm-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.AccountActivationForm-submit {
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// eslint-disable-next-line
|
||||
import React, { useState } from "react";
|
||||
import { connect } from 'react-redux';
|
||||
import { Form, Field } from 'react-final-form';
|
||||
import { OnChange } from 'react-final-form-listeners';
|
||||
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import { InputField, KnownHosts } from 'components';
|
||||
import { FormKey } from 'types';
|
||||
|
||||
import './AccountActivationForm.css';
|
||||
import { useReduxEffect } from 'hooks';
|
||||
import { ServerTypes } from 'store';
|
||||
|
||||
const AccountActivationForm = ({ onSubmit }) => {
|
||||
const [errorMessage, setErrorMessage] = useState(false);
|
||||
|
||||
useReduxEffect(() => {
|
||||
setErrorMessage(true);
|
||||
}, ServerTypes.ACCOUNT_ACTIVATION_FAILED, []);
|
||||
|
||||
const handleOnSubmit = (form) => {
|
||||
setErrorMessage(false);
|
||||
onSubmit(form);
|
||||
}
|
||||
|
||||
const validate = values => {
|
||||
const errors: any = {};
|
||||
|
||||
if (!values.token) {
|
||||
errors.token = 'Required';
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleOnSubmit} validate={validate}>
|
||||
{({ handleSubmit, form }) => {
|
||||
return (
|
||||
<form className="AccountActivationForm" onSubmit={handleSubmit}>
|
||||
<div className="AccountActivationForm-item">
|
||||
<Field label="Token" name="token" component={InputField} autoComplete="off" />
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="AccountActivationForm-error">
|
||||
<Typography color="error">Account activation failed</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button className="AccountActivationForm-submit rounded tall" color="primary" variant="contained" type="submit">
|
||||
Activate Account
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountActivationForm;
|
|
@ -1,3 +1,4 @@
|
|||
export { default as AccountActivationForm } from './AccountActivationForm/AccountActivationForm';
|
||||
export { default as CardImportForm } from './CardImportForm/CardImportForm';
|
||||
export { default as ConnectForm } from './ConnectForm/ConnectForm';
|
||||
export { default as LoginForm } from './LoginForm/LoginForm';
|
||||
|
|
|
@ -93,6 +93,15 @@ export const Actions = {
|
|||
type: Types.REGISTRATION_USERNAME_ERROR,
|
||||
error
|
||||
}),
|
||||
accountAwaitingActivation: () => ({
|
||||
type: Types.ACCOUNT_AWAITING_ACTIVATION,
|
||||
}),
|
||||
accountActivationSuccess: () => ({
|
||||
type: Types.ACCOUNT_ACTIVATION_SUCCESS,
|
||||
}),
|
||||
accountActivationFailed: () => ({
|
||||
type: Types.ACCOUNT_ACTIVATION_FAILED,
|
||||
}),
|
||||
resetPassword: () => ({
|
||||
type: Types.RESET_PASSWORD_REQUESTED,
|
||||
}),
|
||||
|
|
|
@ -80,6 +80,15 @@ export const Dispatch = {
|
|||
registrationUserNameError: (error) => {
|
||||
store.dispatch(Actions.registrationUserNameError(error));
|
||||
},
|
||||
accountAwaitingActivation: () => {
|
||||
store.dispatch(Actions.accountAwaitingActivation());
|
||||
},
|
||||
accountActivationSuccess: () => {
|
||||
store.dispatch(Actions.accountActivationSuccess());
|
||||
},
|
||||
accountActivationFailed: () => {
|
||||
store.dispatch(Actions.accountActivationFailed());
|
||||
},
|
||||
resetPassword: () => {
|
||||
store.dispatch(Actions.resetPassword());
|
||||
},
|
||||
|
|
|
@ -22,6 +22,9 @@ export const Types = {
|
|||
REGISTRATION_EMAIL_ERROR: '[Server] Registration Email Error',
|
||||
REGISTRATION_PASSWORD_ERROR: '[Server] Registration Password Error',
|
||||
REGISTRATION_USERNAME_ERROR: '[Server] Registration Username Error',
|
||||
ACCOUNT_AWAITING_ACTIVATION: '[Server] Account Awaiting Activation',
|
||||
ACCOUNT_ACTIVATION_SUCCESS: '[Server] Account Activation Success',
|
||||
ACCOUNT_ACTIVATION_FAILED: '[Server] Account Activation Failed',
|
||||
RESET_PASSWORD_REQUESTED: '[Server] Reset Password Requested',
|
||||
RESET_PASSWORD_FAILED: '[Server] Reset Password Failed',
|
||||
RESET_PASSWORD_CHALLENGE: '[Server] Reset Password Challenge',
|
||||
|
|
|
@ -137,7 +137,7 @@ export class SessionCommands {
|
|||
webClient.protobuf.sendSessionCommand(sc, raw => {
|
||||
switch (raw.responseCode) {
|
||||
case webClient.protobuf.controller.Response.ResponseCode.RespOk: {
|
||||
const passwordSalt = raw['.Response_PasswordSalt.ext'].passwordSalt;
|
||||
const passwordSalt = raw['.Response_PasswordSalt.ext']?.passwordSalt;
|
||||
SessionCommands.login(passwordSalt);
|
||||
break;
|
||||
}
|
||||
|
@ -236,6 +236,7 @@ export class SessionCommands {
|
|||
|
||||
webClient.protobuf.sendSessionCommand(sc, raw => {
|
||||
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespActivationAccepted) {
|
||||
SessionPersistence.accountActivationSuccess();
|
||||
SessionCommands.login();
|
||||
} else {
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed');
|
||||
|
|
|
@ -78,11 +78,15 @@ export class SessionPersistence {
|
|||
}
|
||||
|
||||
static accountAwaitingActivation() {
|
||||
console.log('Open Modal for Activation Code input');
|
||||
ServerDispatch.accountAwaitingActivation();
|
||||
}
|
||||
|
||||
static accountActivationSuccess() {
|
||||
ServerDispatch.accountActivationSuccess();
|
||||
}
|
||||
|
||||
static accountActivationFailed() {
|
||||
console.log('Account activation failed, show an action here');
|
||||
ServerDispatch.accountActivationFailed();
|
||||
}
|
||||
|
||||
static registrationRequiresEmail() {
|
||||
|
|
Loading…
Reference in a new issue