Newer
Older
CrypticOreWallet / src / layouts / dialogs / MasterPasswordDialog.tsx
@Drew Lemmy Drew Lemmy on 29 Sep 2020 5 KB refactor: forms in ModalDialog
import React, { Component, ReactNode } from "react";

import { withTranslation, WithTranslation, Trans } from "react-i18next";

import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";

import { ModalDialog } from "./ModalDialog";
import { FormikHelpers } from "formik";

import { HelpWalletStorageLink } from "./HelpWalletStorageDialog";

import { Dispatch } from "redux";
import { connect, ConnectedProps } from "react-redux";
import { RootState } from "@store";
import { browseAsGuest, login, setMasterPassword } from "@krist/wallets/WalletManager";

interface OwnProps {
  isLoggedIn: boolean;
  salt?: string;
  tester?: string;
  hasMasterPassword: boolean;
}

const mapStateToProps = (state: RootState): OwnProps => ({
  isLoggedIn: state.walletManager.isLoggedIn,
  salt: state.walletManager.salt,
  tester: state.walletManager.tester,
  hasMasterPassword: state.walletManager.hasMasterPassword
});

const mapDispatchToProps = (dispatch: Dispatch) => {
  return { dispatch };
};

const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = ConnectedProps<typeof connector> & WithTranslation & OwnProps;

interface FormValues {
  password: string;
}

class MasterPasswordDialogComponent extends Component<Props> {
  async onSubmit({ password }: FormValues, helpers: FormikHelpers<FormValues>): Promise<void> {
    const { t } = this.props;

    try {
      if (typeof password !== "string" || password.length === 0) 
        throw new Error("masterPassword.errorPasswordRequired");

      const { dispatch, salt, tester, hasMasterPassword } = this.props;

      if (hasMasterPassword) // Attempt login
        await login(dispatch, salt, tester, password);
      else // Setup a new master password
        await setMasterPassword(dispatch, password);
    } catch (e) { // Catch any errors (usually 'invalid password') and display
      const message = e.message // Translate the error if we can
        ? e.message.startsWith("masterPassword.") ? t(e.message) : e.message
        : t("masterPassword.errorUnknown");

      helpers.setSubmitting(false);
      helpers.setErrors({ password: message });
      console.error(message, e);
    }
  }

  browseAsGuest(): void {
    browseAsGuest(this.props.dispatch);
  }

  render(): ReactNode {
    const { t } = this.props;

    // Don't show the dialog if we're already logged in
    if (this.props.isLoggedIn) return null;

    const { hasMasterPassword } = this.props;
    const body = hasMasterPassword
      ? <p>{t("masterPassword.loginIntro")}</p>
      : <>
        <p className="mb-0">
          <Trans t={t} i18nKey="masterPassword.intro">
            Enter a master password to encrypt your wallets, or browse KristWeb 
            as a guest <HelpWalletStorageLink />. 
          </Trans>
        </p>
        <p><small className="text-muted">
          {t("masterPassword.dontForgetPassword")}
        </small></p>
      </>;

    return (
      <ModalDialog
        show={true}
        title={t("masterPassword.dialogTitle")}
        handleClose={this.browseAsGuest.bind(this)}
        hasFooterCloseButton={false}
        initialValues={{ password: "" }}
        onSubmit={this.onSubmit.bind(this)}
        buttons={({ isSubmitting }) => <>
          {/* Left side, "Browse as guest" button */}
          <Button 
            variant="secondary" 
            style={{ marginRight: "auto" }} 
            onClick={this.browseAsGuest.bind(this)}
            tabIndex={3}
          >
            {t("masterPassword.browseAsGuest")}
          </Button>

          {/* Right side */}
          {hasMasterPassword
            ? <> {/* They have a master password, show login */}
              <Button variant="danger">{t("masterPassword.forgotPassword")}</Button>
              <Button type="submit" variant="primary" disabled={isSubmitting} tabIndex={2}>
                {t("masterPassword.logIn")}
              </Button>
            </>
            : (
              <Button type="submit" variant="primary" disabled={isSubmitting} tabIndex={2}>
                {t("masterPassword.createPassword")}
              </Button>
            )
          }
        </>}
      >
        {({ handleChange, values, errors }) => <>
          {/* Embed the body text, which depends on whether or not this is the 
            first time setting up a master password. */}
          {body}

          {/* Provide a username field for browser autofill */}
          <Form.Control 
            type="username"
            value="Master password" /* Do not translate */
            readOnly={true} 
            hidden={true} 
          />

          {/* Password input */}
          <Form.Control 
            type="password" 
            name="password"
            placeholder={t("masterPassword.passwordPlaceholder")}
            value={values.password}
            onChange={handleChange}
            isInvalid={!!errors.password}
            tabIndex={1} 
            autoFocus={true}
          />
          <Form.Control.Feedback type="invalid">{errors.password}</Form.Control.Feedback>
        </>}
      </ModalDialog>
    );
  }
}

export const MasterPasswordDialog = withTranslation()(connector(MasterPasswordDialogComponent));