diff --git a/web/modals/account/register-modal.react.js b/web/modals/account/register-modal.react.js index be31cb6fd..a2f3a8d29 100644 --- a/web/modals/account/register-modal.react.js +++ b/web/modals/account/register-modal.react.js @@ -1,315 +1,329 @@ // @flow import invariant from 'invariant'; import PropTypes from 'prop-types'; import * as React from 'react'; import { registerActionTypes, register } from 'lib/actions/user-actions'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import { validUsernameRegex, validEmailRegex } from 'lib/shared/account-utils'; import type { RegisterInfo, LogInExtraInfo, RegisterResult, LogInStartingPayload, } from 'lib/types/account-types'; -import type { DispatchActionPromise } from 'lib/utils/action-utils'; -import { connect } from 'lib/utils/redux-utils'; +import { + type DispatchActionPromise, + useDispatchActionPromise, + useServerCall, +} from 'lib/utils/action-utils'; -import type { AppState } from '../../redux/redux-setup'; +import { useSelector } from '../../redux/redux-utils'; import { webLogInExtraInfoSelector } from '../../selectors/account-selectors'; import css from '../../style.css'; import Modal from '../modal.react'; import VerifyEmailModal from './verify-email-modal.react'; -type Props = {| +type BaseProps = {| +setModal: (modal: ?React.Node) => void, - // Redux state +|}; +type Props = {| + ...BaseProps, +inputDisabled: boolean, +logInExtraInfo: () => LogInExtraInfo, - // Redux dispatch functions +dispatchActionPromise: DispatchActionPromise, - // async functions that hit server APIs +register: (registerInfo: RegisterInfo) => Promise, |}; type State = {| +username: string, +email: string, +password: string, +confirmPassword: string, +errorMessage: React.Node, |}; class RegisterModal extends React.PureComponent { usernameInput: ?HTMLInputElement; emailInput: ?HTMLInputElement; passwordInput: ?HTMLInputElement; constructor(props: Props) { super(props); this.state = { username: '', email: '', password: '', confirmPassword: '', errorMessage: null, }; } componentDidMount() { invariant(this.usernameInput, 'username ref unset'); this.usernameInput.focus(); } render() { return (
Username
Email
Password
{this.state.errorMessage}
); } usernameInputRef = (usernameInput: ?HTMLInputElement) => { this.usernameInput = usernameInput; }; emailInputRef = (emailInput: ?HTMLInputElement) => { this.emailInput = emailInput; }; passwordInputRef = (passwordInput: ?HTMLInputElement) => { this.passwordInput = passwordInput; }; onChangeUsername = (event: SyntheticEvent) => { const target = event.target; invariant(target instanceof HTMLInputElement, 'target not input'); this.setState({ username: target.value }); }; onChangeEmail = (event: SyntheticEvent) => { const target = event.target; invariant(target instanceof HTMLInputElement, 'target not input'); this.setState({ email: target.value }); }; onChangePassword = (event: SyntheticEvent) => { const target = event.target; invariant(target instanceof HTMLInputElement, 'target not input'); this.setState({ password: target.value }); }; onChangeConfirmPassword = (event: SyntheticEvent) => { const target = event.target; invariant(target instanceof HTMLInputElement, 'target not input'); this.setState({ confirmPassword: target.value }); }; onSubmit = (event: SyntheticEvent) => { event.preventDefault(); if (this.state.password === '') { this.setState( { password: '', confirmPassword: '', errorMessage: 'empty password', }, () => { invariant(this.passwordInput, 'passwordInput ref unset'); this.passwordInput.focus(); }, ); } else if (this.state.password !== this.state.confirmPassword) { this.setState( { password: '', confirmPassword: '', errorMessage: "passwords don't match", }, () => { invariant(this.passwordInput, 'passwordInput ref unset'); this.passwordInput.focus(); }, ); } else if (this.state.username.search(validUsernameRegex) === -1) { this.setState( { username: '', errorMessage: ( <> Usernames must:
  1. Be at least six characters long,
  2. Start with either a letter or a number,
  3. Contain only letters, numbers, or the characters “-” and “_”.
), }, () => { invariant(this.usernameInput, 'usernameInput ref unset'); this.usernameInput.focus(); }, ); } else if (this.state.email.search(validEmailRegex) === -1) { this.setState( { email: '', errorMessage: 'invalid email address', }, () => { invariant(this.emailInput, 'emailInput ref unset'); this.emailInput.focus(); }, ); } else { const extraInfo = this.props.logInExtraInfo(); this.props.dispatchActionPromise( registerActionTypes, this.registerAction(extraInfo), undefined, ({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload), ); } }; async registerAction(extraInfo: LogInExtraInfo) { try { const result = await this.props.register({ username: this.state.username, email: this.state.email, password: this.state.password, ...extraInfo, }); this.props.setModal(); return result; } catch (e) { if (e.message === 'username_taken') { this.setState( { username: '', errorMessage: 'username already taken', }, () => { invariant(this.usernameInput, 'usernameInput ref unset'); this.usernameInput.focus(); }, ); } else if (e.message === 'email_taken') { this.setState( { email: '', errorMessage: 'email already taken', }, () => { invariant(this.emailInput, 'emailInput ref unset'); this.emailInput.focus(); }, ); } else { this.setState( { username: '', email: '', password: '', confirmPassword: '', errorMessage: 'unknown error', }, () => { invariant(this.usernameInput, 'usernameInput ref unset'); this.usernameInput.focus(); }, ); } throw e; } } clearModal = () => { this.props.setModal(null); }; } RegisterModal.propTypes = { setModal: PropTypes.func.isRequired, inputDisabled: PropTypes.bool.isRequired, logInExtraInfo: PropTypes.func.isRequired, dispatchActionPromise: PropTypes.func.isRequired, register: PropTypes.func.isRequired, }; const loadingStatusSelector = createLoadingStatusSelector(registerActionTypes); -export default connect( - (state: AppState) => ({ - inputDisabled: loadingStatusSelector(state) === 'loading', - logInExtraInfo: webLogInExtraInfoSelector(state), - }), - { register }, -)(RegisterModal); +export default React.memo(function ConnectedRegisterModal( + props: BaseProps, +) { + const inputDisabled = useSelector(loadingStatusSelector) === 'loading'; + const loginExtraInfo = useSelector(webLogInExtraInfoSelector); + const callRegister = useServerCall(register); + const dispatchActionPromise = useDispatchActionPromise(); + + return ( + + ); +});