From b1e2005a735a35395a7fc274b810dc48ad742c03 Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Tue, 6 Jul 2021 17:19:11 -0500 Subject: [PATCH] Add form-field component and new account view (#11450) * add generic form-field component * swap in new form-field component for advanced-gas-controls-row * add new create password view for redesigned onboarding flow * make text additions translatable --- app/_locales/en/messages.json | 12 ++ .../advanced-gas-controls-row.component.js | 97 --------- .../advanced-gas-controls.component.js | 20 +- ui/components/ui/form-field/form-field.js | 134 +++++++++++++ .../ui/form-field/form-field.stories.js | 55 ++++++ ui/components/ui/form-field/index.js | 1 + ui/components/ui/form-field/index.scss | 48 +++++ ui/components/ui/ui-components.scss | 1 + .../onboarding-flow/new-account/index.scss | 34 ++++ .../new-account/new-account.js | 184 ++++++++++++++++++ 10 files changed, 480 insertions(+), 106 deletions(-) delete mode 100644 ui/components/app/advanced-gas-controls/advanced-gas-controls-row.component.js create mode 100644 ui/components/ui/form-field/form-field.js create mode 100644 ui/components/ui/form-field/form-field.stories.js create mode 100644 ui/components/ui/form-field/index.js create mode 100644 ui/components/ui/form-field/index.scss create mode 100644 ui/pages/onboarding-flow/new-account/index.scss create mode 100644 ui/pages/onboarding-flow/new-account/new-account.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 26e4472c2..554cbc1a6 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -471,6 +471,9 @@ "createAccount": { "message": "Create Account" }, + "createNewWallet": { + "message": "Create a new wallet" + }, "createPassword": { "message": "Create Password" }, @@ -1464,6 +1467,12 @@ "passwordNotLongEnough": { "message": "Password not long enough" }, + "passwordSetupDetails": { + "message": "This password will unlock your MetaMask wallet only on this device. MetaMask can not recover this password." + }, + "passwordTermsWarning": { + "message": "I understand that MetaMask cannot recover this password for me. $1" + }, "passwordsDontMatch": { "message": "Passwords Don't Match" }, @@ -1797,6 +1806,9 @@ "settings": { "message": "Settings" }, + "show": { + "message": "Show" + }, "showAdvancedGasInline": { "message": "Advanced gas controls" }, diff --git a/ui/components/app/advanced-gas-controls/advanced-gas-controls-row.component.js b/ui/components/app/advanced-gas-controls/advanced-gas-controls-row.component.js deleted file mode 100644 index cb0bd2c0d..000000000 --- a/ui/components/app/advanced-gas-controls/advanced-gas-controls-row.component.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -import Typography from '../../ui/typography/typography'; -import { - COLORS, - TEXT_ALIGN, - DISPLAY, - TYPOGRAPHY, - FONT_WEIGHT, -} from '../../../helpers/constants/design-system'; - -import NumericInput from '../../ui/numeric-input/numeric-input.component'; -import InfoTooltip from '../../ui/info-tooltip/info-tooltip'; - -export default function AdvancedGasControlsRow({ - titleText, - titleUnit, - tooltipText, - titleDetailText, - error, - onChange, - value, -}) { - return ( -
- -
- ); -} - -AdvancedGasControlsRow.propTypes = { - titleText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - titleUnit: PropTypes.string, - tooltipText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - titleDetailText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - error: PropTypes.string, - onChange: PropTypes.func, - value: PropTypes.number, -}; - -AdvancedGasControlsRow.defaultProps = { - titleText: '', - titleUnit: '', - tooltipText: '', - titleDetailText: '', - error: '', - onChange: undefined, - value: 0, -}; diff --git a/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js b/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js index efd5da258..78403469f 100644 --- a/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js +++ b/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js @@ -7,7 +7,7 @@ import { TYPOGRAPHY, COLORS, } from '../../../helpers/constants/design-system'; -import AdvancedGasControlsRow from './advanced-gas-controls-row.component'; +import FormField from '../../ui/form-field'; export default function AdvancedGasControls() { const t = useContext(I18nContext); @@ -21,22 +21,23 @@ export default function AdvancedGasControls() { return (
- {process.env.SHOW_EIP_1559_UI ? ( <> - } /> - ) : ( <> - )} diff --git a/ui/components/ui/form-field/form-field.js b/ui/components/ui/form-field/form-field.js new file mode 100644 index 000000000..5a4f97ab2 --- /dev/null +++ b/ui/components/ui/form-field/form-field.js @@ -0,0 +1,134 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import Typography from '../typography/typography'; +import Box from '../box/box'; +import { + COLORS, + TEXT_ALIGN, + DISPLAY, + TYPOGRAPHY, + FONT_WEIGHT, +} from '../../../helpers/constants/design-system'; + +import NumericInput from '../numeric-input/numeric-input.component'; +import InfoTooltip from '../info-tooltip/info-tooltip'; + +export default function FormField({ + titleText, + titleUnit, + tooltipText, + titleDetail, + error, + onChange, + value, + numeric, + detailText, + autoFocus, + password, +}) { + return ( +
+ +
+ ); +} + +FormField.propTypes = { + titleText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + titleUnit: PropTypes.string, + tooltipText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + titleDetail: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + error: PropTypes.string, + onChange: PropTypes.func, + value: PropTypes.number, + detailText: PropTypes.string, + autoFocus: PropTypes.bool, + numeric: PropTypes.bool, + password: PropTypes.bool, +}; + +FormField.defaultProps = { + titleText: '', + titleUnit: '', + tooltipText: '', + titleDetail: '', + error: '', + onChange: undefined, + value: 0, + detailText: '', + autoFocus: false, + numeric: false, + password: false, +}; diff --git a/ui/components/ui/form-field/form-field.stories.js b/ui/components/ui/form-field/form-field.stories.js new file mode 100644 index 000000000..b78c908fe --- /dev/null +++ b/ui/components/ui/form-field/form-field.stories.js @@ -0,0 +1,55 @@ +/* eslint-disable react/prop-types */ + +import React, { useState } from 'react'; +import { select } from '@storybook/addon-knobs'; +import FormField from '.'; + +export default { + title: 'FormField', +}; + +export const Plain = ({ ...props }) => { + const options = { text: false, numeric: true }; + const [value, setValue] = useState(''); + return ( +
+ +
+ ); +}; + +export const FormFieldWithTitleDetail = () => { + const [clicked, setClicked] = useState(false); + const detailOptions = { + text:
Detail
, + button: ( + + ), + checkmark: , + }; + return ( + + ); +}; + +export const FormFieldWithError = () => { + return ; +}; diff --git a/ui/components/ui/form-field/index.js b/ui/components/ui/form-field/index.js new file mode 100644 index 000000000..0bcf42e6e --- /dev/null +++ b/ui/components/ui/form-field/index.js @@ -0,0 +1 @@ +export { default } from './form-field'; diff --git a/ui/components/ui/form-field/index.scss b/ui/components/ui/form-field/index.scss new file mode 100644 index 000000000..d7d4cb0b7 --- /dev/null +++ b/ui/components/ui/form-field/index.scss @@ -0,0 +1,48 @@ +.form-field { + margin-bottom: 20px; + + &__heading { + display: flex; + margin-top: 4px; + } + + .info-tooltip { + display: inline-block; + } + + &__heading-detail { + flex-grow: 1; + align-self: center; + } + + &__error, + &__error h6 { + color: $error-1 !important; + padding-top: 6px; + } + + h6 { + padding-bottom: 6px; + margin-inline-end: 6px; + } + + i { + color: #dadada; + font-size: $font-size-h7; + } + + &__input { + width: 100%; + border: solid 1px $ui-3; + padding: 10px; + border-radius: 6px; + + &:focus { + border: solid 2px $primary-1; + } + + &--error { + border-color: $error-1; + } + } +} diff --git a/ui/components/ui/ui-components.scss b/ui/components/ui/ui-components.scss index 0dd1ad72e..0f95c6af8 100644 --- a/ui/components/ui/ui-components.scss +++ b/ui/components/ui/ui-components.scss @@ -35,6 +35,7 @@ @import 'loading-screen/index'; @import 'menu/menu'; @import 'numeric-input/numeric-input'; +@import 'form-field/index'; @import 'page-container/index'; @import 'popover/index'; @import 'pulse-loader/index'; diff --git a/ui/pages/onboarding-flow/new-account/index.scss b/ui/pages/onboarding-flow/new-account/index.scss new file mode 100644 index 000000000..4c8e69bf2 --- /dev/null +++ b/ui/pages/onboarding-flow/new-account/index.scss @@ -0,0 +1,34 @@ +.new-account { + &__wrapper { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + } + + &__link-text { + color: $primary-1; + } + + &__form { + padding: 0 24px; + + &--password-button { + background-color: transparent; + } + + &--submit-button { + padding: 20px; + } + + &--checkmark { + i { + color: $success-1; + } + } + + .form-field__input { + height: 50px; + } + } +} diff --git a/ui/pages/onboarding-flow/new-account/new-account.js b/ui/pages/onboarding-flow/new-account/new-account.js new file mode 100644 index 000000000..2eb351ff8 --- /dev/null +++ b/ui/pages/onboarding-flow/new-account/new-account.js @@ -0,0 +1,184 @@ +import React, { useState, useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { useHistory } from 'react-router-dom'; +import { useNewMetricEvent } from '../../../hooks/useMetricEvent'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import Button from '../../../components/ui/button'; +import Typography from '../../../components/ui/typography'; +import { + TEXT_ALIGN, + TYPOGRAPHY, + JUSTIFY_CONTENT, + FONT_WEIGHT, + ALIGN_ITEMS, +} from '../../../helpers/constants/design-system'; +import { INITIALIZE_SEED_PHRASE_INTRO_ROUTE } from '../../../helpers/constants/routes'; +import FormField from '../../../components/ui/form-field'; +import Box from '../../../components/ui/box'; +import CheckBox from '../../../components/ui/check-box'; + +export default function NewAccount({ onSubmit }) { + const t = useI18nContext(); + const [confirmPassword, setConfirmPassword] = useState(''); + const [password, setPassword] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [confirmPasswordError, setConfirmPasswordError] = useState(''); + const [termsChecked, setTermsChecked] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const history = useHistory(); + + const submitPasswordEvent = useNewMetricEvent({ + event: 'Submit Password', + category: 'Onboarding', + }); + + const isValid = useMemo(() => { + if (!password || !confirmPassword || password !== confirmPassword) { + return false; + } + + if (password.length < 8) { + return false; + } + + return !passwordError && !confirmPasswordError; + }, [password, confirmPassword, passwordError, confirmPasswordError]); + + const handlePasswordChange = (passwordInput) => { + let error = ''; + let confirmError = ''; + if (passwordInput && passwordInput.length < 8) { + error = t('passwordNotLongEnough'); + } + + if (confirmPassword && passwordInput !== confirmPassword) { + confirmError = t('passwordsDontMatch'); + } + + setPassword(passwordInput); + setPasswordError(error); + setConfirmPasswordError(confirmError); + }; + + const handleConfirmPasswordChange = (confirmPasswordInput) => { + let error = ''; + if (password !== confirmPasswordInput) { + error = t('passwordsDontMatch'); + } + + setConfirmPassword(confirmPasswordInput); + setConfirmPasswordError(error); + }; + + const handleCreate = async (event) => { + event.preventDefault(); + + if (!isValid) { + return; + } + try { + if (onSubmit) { + await onSubmit(password); + } + submitPasswordEvent(); + history.push(INITIALIZE_SEED_PHRASE_INTRO_ROUTE); + } catch (error) { + setPasswordError(error.message); + } + }; + + return ( +
+ + {t('createPassword')} + + + {t('passwordSetupDetails')} + + +
+ { + e.preventDefault(); + setShowPassword(!showPassword); + }} + > + {showPassword ? t('hide') : t('show')} + + } + /> + + +
+ ) + } + /> + + setTermsChecked(!termsChecked)} + checked={termsChecked} + /> + + {t('passwordTermsWarning', [ + e.stopPropagation()} + key="new-account__link-text" + href="https://metamask.io/terms.html" + target="_blank" + rel="noopener noreferrer" + > + + {t('learnMore')} + + , + ])} + + + + + +
+ ); +} + +NewAccount.propTypes = { + onSubmit: PropTypes.func, +};