1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Onboarding V2 Creation Successful view (#12248)

* add creation-successful onboarding view
This commit is contained in:
Alex Donesky 2021-10-11 09:43:25 -05:00 committed by GitHub
parent b242d950a0
commit a8ec9ada2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 263 additions and 21 deletions

View File

@ -935,6 +935,9 @@
"message": "File import not working? Click here!",
"description": "Helps user import their account from a JSON file"
},
"followUsOnTwitter": {
"message": "Follow us on Twitter"
},
"forbiddenIpfsGateway": {
"message": "Forbidden IPFS Gateway: Please specify a CID gateway"
},
@ -1793,6 +1796,9 @@
"rejected": {
"message": "Rejected"
},
"remember": {
"message": "Remember:"
},
"remindMeLater": {
"message": "Remind me later"
},
@ -2855,6 +2861,26 @@
"walletConnectionGuide": {
"message": "our hardware wallet connection guide"
},
"walletCreationSuccessDetail": {
"message": "Youve successfully protected your wallet. Keep your Secret Recovery Phrase safe and secret -- its your responsibility!"
},
"walletCreationSuccessReminder1": {
"message": "MetaMask cant recover your Secret Recovery Phrase."
},
"walletCreationSuccessReminder2": {
"message": "MetaMask will never ask you for your Secret Recovery Phrase."
},
"walletCreationSuccessReminder3": {
"message": "$1 with anyone or risk your funds being stolen",
"description": "$1 is separated as walletCreationSuccessReminder3BoldSection so that we can bold it"
},
"walletCreationSuccessReminder3BoldSection": {
"message": "Never share your Secret Recovery Phrase",
"description": "This string is localized separately from walletCreationSuccessReminder3 so that we can bold it"
},
"walletCreationSuccessTitle": {
"message": "Wallet creation successful"
},
"walletSeed": {
"message": "Secret Recovery Phrase"
},

BIN
app/images/tada.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
app/images/twitter-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

View File

@ -1 +1 @@
export { default } from './step-progress-bar';
export { default, stages } from './step-progress-bar';

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { useI18nContext } from '../../../hooks/useI18nContext';
import Box from '../../ui/box';
const stages = {
export const stages = {
PASSWORD_CREATE: 1,
SEED_PHRASE_VIDEO: 2,
SEED_PHRASE_REVIEW: 3,

View File

@ -15,7 +15,10 @@ import {
const ValidSize = PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
const ArrayOfValidSizes = PropTypes.arrayOf(ValidSize);
const MultipleSizes = PropTypes.oneOfType([ValidSize, ArrayOfValidSizes]);
export const MultipleSizes = PropTypes.oneOfType([
ValidSize,
ArrayOfValidSizes,
]);
function generateSizeClasses(baseClass, type, main, top, right, bottom, left) {
const arr = Array.isArray(main) ? main : [];

View File

@ -1 +1 @@
export { default } from './box';
export { default, MultipleSizes } from './box';

View File

@ -8,7 +8,7 @@ import {
TEXT_ALIGN,
TYPOGRAPHY,
} from '../../../helpers/constants/design-system';
import Box from '../box';
import Box, { MultipleSizes } from '../box';
const { H6, H7, H8, H9 } = TYPOGRAPHY;
@ -22,6 +22,7 @@ export default function Typography({
fontStyle = 'normal',
align,
boxProps = {},
margin = [1, 0],
}) {
const computedClassName = classnames(
'typography',
@ -44,7 +45,7 @@ export default function Typography({
}
return (
<Box margin={[1, 0]} {...boxProps}>
<Box margin={margin} {...boxProps}>
{(boxClassName) => (
<Tag className={classnames(boxClassName, computedClassName)}>
{children}
@ -63,6 +64,7 @@ Typography.propTypes = {
boxProps: PropTypes.shape({
...Box.propTypes,
}),
margin: MultipleSizes,
fontWeight: PropTypes.oneOf(Object.values(FONT_WEIGHT)),
fontStyle: PropTypes.oneOf(Object.values(FONT_STYLE)),
tag: PropTypes.oneOf([

View File

@ -16,8 +16,11 @@ import { INITIALIZE_SEED_PHRASE_INTRO_ROUTE } from '../../../helpers/constants/r
import FormField from '../../../components/ui/form-field';
import Box from '../../../components/ui/box';
import CheckBox from '../../../components/ui/check-box';
import StepProgressBar, {
stages,
} from '../../../components/app/step-progress-bar';
export default function NewAccount({ onSubmit }) {
export default function CreatePassword({ onSubmit }) {
const t = useI18nContext();
const [confirmPassword, setConfirmPassword] = useState('');
const [password, setPassword] = useState('');
@ -88,7 +91,8 @@ export default function NewAccount({ onSubmit }) {
};
return (
<div className="new-account__wrapper">
<div className="create-password__wrapper">
<StepProgressBar stage={stages.PASSWORD_CREATE} />
<Typography variant={TYPOGRAPHY.H2} fontWeight={FONT_WEIGHT.BOLD}>
{t('createPassword')}
</Typography>
@ -104,7 +108,7 @@ export default function NewAccount({ onSubmit }) {
marginTop={3}
padding={[0, 12]}
>
<form className="new-account__form" onSubmit={handleCreate}>
<form className="create-password__form" onSubmit={handleCreate}>
<FormField
autoFocus
error={passwordError}
@ -114,7 +118,7 @@ export default function NewAccount({ onSubmit }) {
value={password}
titleDetail={
<button
className="new-account__form--password-button"
className="create-password__form--password-button"
type="button"
onClick={(e) => {
e.preventDefault();
@ -133,7 +137,7 @@ export default function NewAccount({ onSubmit }) {
value={confirmPassword}
titleDetail={
isValid && (
<div className="new-account__form--checkmark">
<div className="create-password__form--checkmark">
<i className="fas fa-check" />
</div>
)
@ -152,12 +156,12 @@ export default function NewAccount({ onSubmit }) {
{t('passwordTermsWarning', [
<a
onClick={(e) => e.stopPropagation()}
key="new-account__link-text"
key="create-password__link-text"
href="https://metamask.io/terms.html"
target="_blank"
rel="noopener noreferrer"
>
<span className="new-account__link-text">
<span className="create-password__link-text">
{t('learnMore')}
</span>
</a>,
@ -166,7 +170,7 @@ export default function NewAccount({ onSubmit }) {
</Box>
<Button
type="primary"
className="new-account__form--submit-button"
className="create-password__form--submit-button"
disabled={!isValid || !termsChecked}
onClick={handleCreate}
>
@ -178,6 +182,6 @@ export default function NewAccount({ onSubmit }) {
);
}
NewAccount.propTypes = {
CreatePassword.propTypes = {
onSubmit: PropTypes.func,
};

View File

@ -1,9 +1,11 @@
.new-account {
.create-password {
&__wrapper {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
max-width: 600px;
max-height: 750px;
}
&__link-text {

View File

@ -0,0 +1,88 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import Box from '../../../components/ui/box';
import Typography from '../../../components/ui/typography';
import Button from '../../../components/ui/button';
import {
FONT_WEIGHT,
TEXT_ALIGN,
TYPOGRAPHY,
} from '../../../helpers/constants/design-system';
import { useI18nContext } from '../../../hooks/useI18nContext';
import {
ONBOARDING_PIN_EXTENSION_ROUTE,
ONBOARDING_PRIVACY_SETTINGS_ROUTE,
} from '../../../helpers/constants/routes';
export default function CreationSuccessful() {
const history = useHistory();
const t = useI18nContext();
return (
<div className="creation-successful">
<Box textAlign={TEXT_ALIGN.CENTER} margin={6}>
<img src="./images/tada.png" />
<Typography
variant={TYPOGRAPHY.H2}
fontWeight={FONT_WEIGHT.BOLD}
margin={6}
>
{t('walletCreationSuccessTitle')}
</Typography>
<Typography variant={TYPOGRAPHY.H4}>
{t('walletCreationSuccessDetail')}
</Typography>
</Box>
<Typography variant={TYPOGRAPHY.H4}>{t('remember')}</Typography>
<ul>
<li>
<Typography variant={TYPOGRAPHY.H4}>
{t('walletCreationSuccessReminder1')}
</Typography>
</li>
<li>
<Typography variant={TYPOGRAPHY.H4}>
{t('walletCreationSuccessReminder2')}
</Typography>
</li>
<li>
<Typography variant={TYPOGRAPHY.H4}>
{t('walletCreationSuccessReminder3', [
<span
key="creation-successful__bold"
className="creation-successful__bold"
>
{t('walletCreationSuccessReminder3BoldSection')}
</span>,
])}
</Typography>
</li>
<li>
<Button
href="https://community.metamask.io/t/what-is-a-secret-recovery-phrase-and-how-to-keep-your-crypto-wallet-secure/3440"
target="_blank"
type="link"
rel="noopener noreferrer"
>
{t('learnMore')}
</Button>
</li>
</ul>
<Box marginTop={6} className="creation-successful__actions">
<Button
type="link"
onClick={() => history.push(ONBOARDING_PRIVACY_SETTINGS_ROUTE)}
>
{t('setAdvancedPrivacySettings')}
</Button>
<Button
type="primary"
large
rounded
onClick={() => history.push(ONBOARDING_PIN_EXTENSION_ROUTE)}
>
{t('done')}
</Button>
</Box>
</div>
);
}

View File

@ -0,0 +1,15 @@
import React from 'react';
import CreationSuccessful from './creation-successful';
export default {
title: 'Onboarding - Creation Successful',
id: __filename,
};
export const Base = () => {
return (
<div style={{ maxHeight: '2000px' }}>
<CreationSuccessful />
</div>
);
};

View File

@ -0,0 +1,33 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import reactRouterDom from 'react-router-dom';
import {
ONBOARDING_PIN_EXTENSION_ROUTE,
ONBOARDING_PRIVACY_SETTINGS_ROUTE,
} from '../../../helpers/constants/routes';
import { renderWithProvider } from '../../../../test/jest';
import CreationSuccessful from './creation-successful';
describe('Creation Successful Onboarding View', () => {
const pushMock = jest.fn();
beforeAll(() => {
jest
.spyOn(reactRouterDom, 'useHistory')
.mockImplementation()
.mockReturnValue({ push: pushMock });
});
it('should redirect to pin-extension view when "Done" button is clicked', () => {
const { getByText } = renderWithProvider(<CreationSuccessful />);
const doneButton = getByText('Done');
fireEvent.click(doneButton);
expect(pushMock).toHaveBeenCalledWith(ONBOARDING_PIN_EXTENSION_ROUTE);
});
it('should redirect to privacy-settings view when "Set advanced privacy settings" button is clicked', () => {
const { getByText } = renderWithProvider(<CreationSuccessful />);
const privacySettingsButton = getByText('Set advanced privacy settings');
fireEvent.click(privacySettingsButton);
expect(pushMock).toHaveBeenCalledWith(ONBOARDING_PRIVACY_SETTINGS_ROUTE);
});
});

View File

@ -0,0 +1,38 @@
.creation-successful {
img {
align-self: center;
}
max-width: 575px;
ul {
list-style-type: disc;
max-width: 500px;
}
li {
margin-left: 25px;
a {
justify-content: flex-start;
padding: 0;
}
}
&__bold {
font-weight: bold;
}
&__actions {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
button {
margin-top: 14px;
max-width: 60%;
padding: 18px 0;
}
}
}

View File

@ -1,8 +1,9 @@
@import 'recovery-phrase/index';
@import 'new-account/index';
@import 'onboarding-app-header/index';
@import 'secure-your-wallet/index';
@import 'privacy-settings/index';
@import 'create-password/index';
@import 'creation-successful/index';
.onboarding-flow {
width: 100%;
@ -18,4 +19,13 @@
border: 1px solid $Grey-100;
border-radius: 20px;
}
&__twitter-button {
margin-bottom: 40px;
vertical-align: middle;
img {
margin-left: 5px;
}
}
}

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Switch, Route, useHistory } from 'react-router-dom';
import { Switch, Route, useHistory, useLocation } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import Unlock from '../unlock-page';
import {
@ -10,6 +10,7 @@ import {
DEFAULT_ROUTE,
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
ONBOARDING_PRIVACY_SETTINGS_ROUTE,
ONBOARDING_COMPLETION_ROUTE,
} from '../../helpers/constants/routes';
import {
getCompletedOnboarding,
@ -22,17 +23,22 @@ import {
unlockAndGetSeedPhrase,
} from '../../store/actions';
import { getFirstTimeFlowTypeRoute } from '../../selectors';
import Button from '../../components/ui/button';
import { useI18nContext } from '../../hooks/useI18nContext';
import OnboardingFlowSwitch from './onboarding-flow-switch/onboarding-flow-switch';
import NewAccount from './new-account/new-account';
import CreatePassword from './create-password/create-password';
import ReviewRecoveryPhrase from './recovery-phrase/review-recovery-phrase';
import SecureYourWallet from './secure-your-wallet/secure-your-wallet';
import ConfirmRecoveryPhrase from './recovery-phrase/confirm-recovery-phrase';
import PrivacySettings from './privacy-settings/privacy-settings';
import CreationSuccessful from './creation-successful/creation-successful';
export default function OnboardingFlow() {
const [seedPhrase, setSeedPhrase] = useState('');
const dispatch = useDispatch();
const currentLocation = useLocation();
const history = useHistory();
const t = useI18nContext();
const isInitialized = useSelector(getIsInitialized);
const isUnlocked = useSelector(getIsUnlocked);
const completedOnboarding = useSelector(getCompletedOnboarding);
@ -43,7 +49,7 @@ export default function OnboardingFlow() {
// For ONBOARDING_V2 dev purposes,
// Remove when ONBOARDING_V2 dev complete
if (process.env.ONBOARDING_V2) {
history.push(ONBOARDING_PRIVACY_SETTINGS_ROUTE);
history.push(ONBOARDING_COMPLETION_ROUTE);
return;
}
@ -85,7 +91,7 @@ export default function OnboardingFlow() {
<Route
path={ONBOARDING_CREATE_PASSWORD_ROUTE}
render={(routeProps) => (
<NewAccount
<CreatePassword
{...routeProps}
createNewAccount={handleCreateNewAccount}
/>
@ -114,9 +120,24 @@ export default function OnboardingFlow() {
path={ONBOARDING_PRIVACY_SETTINGS_ROUTE}
component={PrivacySettings}
/>
<Route
path={ONBOARDING_COMPLETION_ROUTE}
component={CreationSuccessful}
/>
<Route exact path="*" component={OnboardingFlowSwitch} />
</Switch>
</div>
{currentLocation?.pathname === ONBOARDING_COMPLETION_ROUTE && (
<Button
className="onboarding-flow__twitter-button"
type="link"
href="https://twitter.com/MetaMask"
target="_blank"
>
<span>{t('followUsOnTwitter')}</span>
<img src="images/twitter-icon.png" />
</Button>
)}
</div>
);
}