diff --git a/web/settings/account-delete-modal.css b/web/settings/account-delete-modal.css index eae24cd1e..712f26cb9 100644 --- a/web/settings/account-delete-modal.css +++ b/web/settings/account-delete-modal.css @@ -1,143 +1,65 @@ 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 { 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/account-delete-modal.react.js b/web/settings/account-delete-modal.react.js index a1b47e293..6de7374b1 100644 --- a/web/settings/account-delete-modal.react.js +++ b/web/settings/account-delete-modal.react.js @@ -1,440 +1,192 @@ // @flow -import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { - deleteAccountActionTypes, deleteAccount, - changeUserPasswordActionTypes, - changeUserPassword, - logOut, - logOutActionTypes, + deleteAccountActionTypes, } 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 { PreRequestUserState } from 'lib/types/session-types'; +import type { DispatchActionPromise } from 'lib/utils/action-utils'; 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 './account-delete-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, +popModal: () => void, }; type State = { - +newPassword: string, - +confirmNewPassword: string, +currentPassword: string, +errorMessage: string, - +currentTabType: TabType, }; class AccountDeleteModal 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; + invariant(this.currentPasswordInput, 'newPasswordInput ref unset'); + this.currentPasswordInput.focus(); } - onLogOut = (event: SyntheticEvent) => { - event.preventDefault(); - this.props.dispatchActionPromise(logOutActionTypes, this.logOut()); - }; - - logOut = async () => { - await this.props.logOut(this.props.preRequestUserState); - this.props.popModal(); - }; - render() { const { inputDisabled } = this.props; - let mainContent = null; - if (this.state.currentTabType === 'general') { - mainContent = ( -
    -
    -
    Username
    -
    {this.username}
    -
    -
    -
    New password
    -
    -
    - -
    -
    - -
    -
    -
    -
    - ); - } else if (this.state.currentTabType === 'delete') { - mainContent = ( -

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

    - ); - } + const 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.popModal(); - } 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.popModal(); 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 ConnectedAccountDeleteModal: React.ComponentType<{}> = React.memo<{}>( function ConnectedAccountDeleteModal(): React.Node { - const currentUserInfo = useSelector(state => state.currentUserInfo); const preRequestUserState = useSelector(preRequestUserStateSelector); const inputDisabled = useSelector( - state => - deleteAccountLoadingStatusSelector(state) === 'loading' || - changeUserPasswordLoadingStatusSelector(state) === 'loading', + state => deleteAccountLoadingStatusSelector(state) === 'loading', ); const callDeleteAccount = useServerCall(deleteAccount); - const callChangeUserPassword = useServerCall(changeUserPassword); const dispatchActionPromise = useDispatchActionPromise(); - const boundLogOut = useServerCall(logOut); const modalContext = useModalContext(); return ( ); }, ); export default ConnectedAccountDeleteModal;