diff --git a/web/settings/password-change-modal.css b/web/settings/password-change-modal.css index 9b0230e25..64009b444 100644 --- a/web/settings/password-change-modal.css +++ b/web/settings/password-change-modal.css @@ -1,144 +1,92 @@ div.modal-body { padding: 6px 6px; width: 100%; box-sizing: border-box; background-color: var(--modal-bg); border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; flex: 1; display: flex; flex-direction: column; } -div.resized-modal-body { - min-height: 250px; -} div.modal-body p { padding: 1px 3px 4px 3px; font-size: 14px; text-align: center; } -div.modal-body p.form-pre-footer { - padding-top: 5px; - font-size: 12px; - font-style: italic; -} -div.modal-body textarea { - margin: 3px; -} -div.modal-body textarea { - font-size: 14px; - padding: 1px; - width: 175px; -} -div.large-modal-container div.modal-body textarea { - width: 275px; -} div.modal-body p.confirm-account-password { color: #777777; margin-bottom: 4px; color: var(--fg); } div.modal-body div.form-footer { display: flex; flex-direction: row-reverse; justify-content: space-between; padding-top: 8px; } div.modal-body div.form-footer div.modal-form-error { font-size: 12px; color: red; font-style: italic; padding-left: 6px; align-self: center; } -div.modal-body div.form-footer div.modal-form-error ol { - padding-left: 20px; -} div.modal-body div.form-title { display: inline-block; text-align: right; padding-right: 5px; padding-top: 5px; font-size: 14px; font-weight: 600; vertical-align: top; width: 110px; color: var(--fg); } div.large-modal-container div.modal-body div.form-title { width: 140px; } div.modal-body div.form-content { display: inline-block; font-family: var(--font-stack); color: var(--fg); } div.modal-body div.form-content input { margin-bottom: 4px; } -div.modal-body div.form-subtitle { - font-size: 12px; - padding-left: 4px; - font-style: italic; -} div.form-text { display: flex; align-items: baseline; } div.form-text > div.form-title { vertical-align: initial; flex-shrink: 0; } div.form-text > div.form-content { margin-left: 3px; margin-bottom: 3px; word-break: break-word; } div.form-text > div.form-float-title { float: left; text-align: right; padding-right: 5px; font-size: 14px; font-weight: 600; width: 110px; } div.form-text > div.form-float-content { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 14px; padding: 1px 20px 3px 4px; margin-top: 5px; } -.italic { - font-style: italic; - color: var(--fg); -} - -ul.tab-panel { - background-color: var(--modal-bg); - padding-left: 10px; - padding-top: 5px; -} -ul.tab-panel > li { - display: inline-block; - list-style-type: none; - font-size: 13px; - font-weight: 600; - cursor: pointer; - padding: 3px 10px 3px 10px; -} -ul.tab-panel > li > a { - color: #555555; -} -ul.tab-panel > li.delete-tab > a { - color: #ff0000 !important; -} - div.user-settings-current-password { padding-top: 4px; margin-top: 5px; } diff --git a/web/settings/password-change-modal.js b/web/settings/password-change-modal.js index 66ae9c9e6..5f2510607 100644 --- a/web/settings/password-change-modal.js +++ b/web/settings/password-change-modal.js @@ -1,440 +1,282 @@ // @flow -import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { - deleteAccountActionTypes, - deleteAccount, changeUserPasswordActionTypes, changeUserPassword, - logOut, - logOutActionTypes, } from 'lib/actions/user-actions'; -import { preRequestUserStateSelector } from 'lib/selectors/account-selectors'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; -import type { LogOutResult } from 'lib/types/account-types'; -import { type PreRequestUserState } from 'lib/types/session-types'; import { type PasswordUpdate, type CurrentUserInfo, } from 'lib/types/user-types'; import { type DispatchActionPromise, useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils'; import Button from '../components/button.react'; import Input from '../modals/input.react'; import { useModalContext } from '../modals/modal-provider.react'; import Modal from '../modals/modal.react'; import { useSelector } from '../redux/redux-utils'; import css from './password-change-modal.css'; -type TabType = 'general' | 'delete'; -type TabProps = { - +name: string, - +tabType: TabType, - +selected: boolean, - +onClick: (tabType: TabType) => void, -}; -class Tab extends React.PureComponent { - render() { - const { selected, name, tabType } = this.props; - const classNamesForTab = classNames({ - [css['current-tab']]: selected, - [css['delete-tab']]: selected && tabType === 'delete', - }); - return ( -
  • - {name} -
  • - ); - } - - onClick = () => { - return this.props.onClick(this.props.tabType); - }; -} - type Props = { +currentUserInfo: ?CurrentUserInfo, - +preRequestUserState: PreRequestUserState, +inputDisabled: boolean, +dispatchActionPromise: DispatchActionPromise, - +deleteAccount: ( - password: string, - preRequestUserState: PreRequestUserState, - ) => Promise, +changeUserPassword: (passwordUpdate: PasswordUpdate) => Promise, - +logOut: (preRequestUserState: PreRequestUserState) => Promise, +clearModal: () => void, }; type State = { +newPassword: string, +confirmNewPassword: string, +currentPassword: string, +errorMessage: string, - +currentTabType: TabType, }; class PasswordChangeModal extends React.PureComponent { newPasswordInput: ?HTMLInputElement; currentPasswordInput: ?HTMLInputElement; constructor(props: Props) { super(props); this.state = { newPassword: '', confirmNewPassword: '', currentPassword: '', errorMessage: '', - currentTabType: 'general', }; } componentDidMount() { invariant(this.newPasswordInput, 'newPasswordInput ref unset'); this.newPasswordInput.focus(); } get username() { return this.props.currentUserInfo && !this.props.currentUserInfo.anonymous ? this.props.currentUserInfo.username : undefined; } - onLogOut = (event: SyntheticEvent) => { - event.preventDefault(); - this.props.dispatchActionPromise(logOutActionTypes, this.logOut()); - }; - - logOut = async () => { - await this.props.logOut(this.props.preRequestUserState); - this.props.clearModal(); - }; - render() { const { inputDisabled } = this.props; - let mainContent = null; - if (this.state.currentTabType === 'general') { - mainContent = ( + const mainContent = ( +
    +
    +
    Username
    +
    {this.username}
    +
    -
    -
    Username
    -
    {this.username}
    -
    -
    -
    New password
    -
    -
    - -
    -
    - -
    +
    New password
    +
    +
    + +
    +
    +
    - ); - } else if (this.state.currentTabType === 'delete') { - mainContent = ( -

    - Your account will be permanently deleted. There is no way to reverse - this. -

    - ); - } +
    + ); - let buttons = null; - if (this.state.currentTabType === 'delete') { - buttons = ( - - ); - } else { - buttons = ( - <> - - - - ); - } + const buttons = ( + + ); let errorMsg; if (this.state.errorMessage) { errorMsg = (
    {this.state.errorMessage}
    ); } return ( -
      - - -
    {mainContent}

    Please enter your current password to confirm your identity

    Current password
    {buttons} {errorMsg}
    ); } newPasswordInputRef = (newPasswordInput: ?HTMLInputElement) => { this.newPasswordInput = newPasswordInput; }; currentPasswordInputRef = (currentPasswordInput: ?HTMLInputElement) => { this.currentPasswordInput = currentPasswordInput; }; - setTab = (tabType: TabType) => { - this.setState({ currentTabType: tabType }); - }; - onChangeNewPassword = (event: SyntheticEvent) => { const target = event.target; invariant(target instanceof HTMLInputElement, 'target not input'); this.setState({ newPassword: target.value }); }; onChangeConfirmNewPassword = (event: SyntheticEvent) => { const target = event.target; invariant(target instanceof HTMLInputElement, 'target not input'); this.setState({ confirmNewPassword: target.value }); }; onChangeCurrentPassword = (event: SyntheticEvent) => { const target = event.target; invariant(target instanceof HTMLInputElement, 'target not input'); this.setState({ currentPassword: target.value }); }; onSubmit = (event: SyntheticEvent) => { event.preventDefault(); if (this.state.newPassword === '') { this.setState( { newPassword: '', confirmNewPassword: '', errorMessage: 'empty password', - currentTabType: 'general', }, () => { invariant(this.newPasswordInput, 'newPasswordInput ref unset'); this.newPasswordInput.focus(); }, ); } else if (this.state.newPassword !== this.state.confirmNewPassword) { this.setState( { newPassword: '', confirmNewPassword: '', errorMessage: "passwords don't match", - currentTabType: 'general', }, () => { invariant(this.newPasswordInput, 'newPasswordInput ref unset'); this.newPasswordInput.focus(); }, ); return; } this.props.dispatchActionPromise( changeUserPasswordActionTypes, this.changeUserSettingsAction(), ); }; async changeUserSettingsAction() { try { await this.props.changeUserPassword({ updatedFields: { password: this.state.newPassword, }, currentPassword: this.state.currentPassword, }); this.props.clearModal(); } catch (e) { if (e.message === 'invalid_credentials') { this.setState( { currentPassword: '', errorMessage: 'wrong current password', }, () => { invariant( this.currentPasswordInput, 'currentPasswordInput ref unset', ); this.currentPasswordInput.focus(); }, ); } else { this.setState( { newPassword: '', confirmNewPassword: '', currentPassword: '', errorMessage: 'unknown error', - currentTabType: 'general', }, () => { invariant(this.newPasswordInput, 'newPasswordInput ref unset'); this.newPasswordInput.focus(); }, ); } throw e; } } - - onDelete = (event: SyntheticEvent) => { - event.preventDefault(); - this.props.dispatchActionPromise( - deleteAccountActionTypes, - this.deleteAction(), - ); - }; - - async deleteAction() { - try { - const response = await this.props.deleteAccount( - this.state.currentPassword, - this.props.preRequestUserState, - ); - this.props.clearModal(); - return response; - } catch (e) { - const errorMessage = - e.message === 'invalid_credentials' - ? 'wrong password' - : 'unknown error'; - this.setState( - { - currentPassword: '', - errorMessage: errorMessage, - }, - () => { - invariant( - this.currentPasswordInput, - 'currentPasswordInput ref unset', - ); - this.currentPasswordInput.focus(); - }, - ); - throw e; - } - } } -const deleteAccountLoadingStatusSelector = createLoadingStatusSelector( - deleteAccountActionTypes, -); const changeUserPasswordLoadingStatusSelector = createLoadingStatusSelector( changeUserPasswordActionTypes, ); const ConnectedPasswordChangeModal: React.ComponentType<{}> = React.memo<{}>( function ConnectedPasswordChangeModal(): React.Node { const currentUserInfo = useSelector(state => state.currentUserInfo); - const preRequestUserState = useSelector(preRequestUserStateSelector); const inputDisabled = useSelector( - state => - deleteAccountLoadingStatusSelector(state) === 'loading' || - changeUserPasswordLoadingStatusSelector(state) === 'loading', + state => changeUserPasswordLoadingStatusSelector(state) === 'loading', ); - const callDeleteAccount = useServerCall(deleteAccount); const callChangeUserPassword = useServerCall(changeUserPassword); const dispatchActionPromise = useDispatchActionPromise(); - const boundLogOut = useServerCall(logOut); const modalContext = useModalContext(); return ( ); }, ); export default ConnectedPasswordChangeModal;