Update FirstRun for UI-2 (#7195)

* WIP

* Update after @parity/ui update

* Update to latest

* Update semver for @parity

* Update & -> &
This commit is contained in:
Jaco Greeff 2017-12-05 10:44:34 +01:00 committed by GitHub
parent e08893fcf1
commit 1291a24e4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 480 deletions

73
js/package-lock.json generated
View File

@ -40,12 +40,6 @@
}
}
},
"@parity/dapp-accounts": {
"version": "github:paritytech/dapp-accounts#ca55be1774563862e7b0cc72740d0747a60036b8",
"requires": {
"@parity/dapp-vaults": "github:paritytech/dapp-vaults#1bd5de3994227e6b733e7b3e985117a59f0ad638"
}
},
"@parity/dapp-console": {
"version": "github:paritytech/dapp-console#f48baba86be6ee2f04ab208731c9529c7b6f92f8",
"dev": true
@ -529,6 +523,18 @@
"integrity": "sha1-jgOPbdsUvXZa4fS1IW4SCUUR4NA=",
"dev": true
},
"semantic-ui-react": {
"version": "0.76.0",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.76.0.tgz",
"integrity": "sha512-CdiIT8n7ZwUlytZkYsQMnaVGmoIZI/mVs4lijvLcR568kcnlRkYYaFKhMLq5tFDQU6+QhdTD+8WebF7ov0Ql6Q==",
"dev": true,
"requires": {
"babel-runtime": "6.26.0",
"classnames": "2.2.5",
"lodash": "4.17.4",
"prop-types": "15.6.0"
}
},
"webrtc-adapter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-2.1.0.tgz",
@ -1027,6 +1033,26 @@
"integrity": "sha1-jgOPbdsUvXZa4fS1IW4SCUUR4NA=",
"dev": true
},
"semantic-ui-react": {
"version": "0.76.0",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.76.0.tgz",
"integrity": "sha512-CdiIT8n7ZwUlytZkYsQMnaVGmoIZI/mVs4lijvLcR568kcnlRkYYaFKhMLq5tFDQU6+QhdTD+8WebF7ov0Ql6Q==",
"dev": true,
"requires": {
"babel-runtime": "6.26.0",
"classnames": "2.2.5",
"lodash": "4.17.4",
"prop-types": "15.6.0"
},
"dependencies": {
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
"dev": true
}
}
},
"webrtc-adapter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-2.1.0.tgz",
@ -1073,7 +1099,8 @@
"dev": true
},
"@parity/dapp-vaults": {
"version": "github:paritytech/dapp-vaults#1bd5de3994227e6b733e7b3e985117a59f0ad638"
"version": "github:paritytech/dapp-vaults#1bd5de3994227e6b733e7b3e985117a59f0ad638",
"dev": true
},
"@parity/dapp-web": {
"version": "github:paritytech/dapp-web#6ea1fe0c7c0d01c43788dbf6a11d1573a443ce1d",
@ -1112,16 +1139,16 @@
}
},
"@parity/plugin-signer-account": {
"version": "github:paritytech/plugin-signer-account#bd647f1163ca49fe52aedb7c69ae1bed6f1d14b7"
"version": "github:paritytech/plugin-signer-account#e151e3ca4e0d51aec93219df3661212cc15aa4cc"
},
"@parity/plugin-signer-default": {
"version": "github:paritytech/plugin-signer-default#1440c8c750b3d824a5569f4302102ce64fc63b72"
"version": "github:paritytech/plugin-signer-default#0d596844063849f7b3cce8fa2d8f7cc56e8caa58"
},
"@parity/plugin-signer-hardware": {
"version": "github:paritytech/plugin-signer-hardware#8fc74af1e700afb883ea4e09aeee96a3ca4f288f"
"version": "github:paritytech/plugin-signer-hardware#ae9c943baf177c15bf478429f26c169fea198ef8"
},
"@parity/plugin-signer-qr": {
"version": "github:paritytech/plugin-signer-qr#fe2a63955c636399719ba2aa6c58710de6cb1b89"
"version": "github:paritytech/plugin-signer-qr#c16423de5b8a8f68ebd5f1e78e084fa959329a9f"
},
"@parity/shared": {
"version": "2.2.7",
@ -1182,9 +1209,9 @@
}
},
"@parity/ui": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@parity/ui/-/ui-3.0.4.tgz",
"integrity": "sha512-/IS+6Qxr5HGAvdaB0xud9lU2c48Crg2dY7yzkb7V+99qvqYV/OAI31IffaBOt3UbLz/QWF+Da+ZOWT96eIVlAg==",
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@parity/ui/-/ui-3.0.7.tgz",
"integrity": "sha512-FkVb6DWja56uaNN+dG/hNH3c1P4hlA8Sg/e65b4hKyZe3PQP7Gko34wJ0IaP5FEeAWQC/yg0SgrQvc8CqPjeSg==",
"requires": {
"@parity/api": "2.1.5",
"@parity/etherscan": "2.1.3",
@ -1252,6 +1279,17 @@
"requires": {
"hoist-non-react-statics": "2.3.1"
}
},
"semantic-ui-react": {
"version": "0.76.0",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.76.0.tgz",
"integrity": "sha512-CdiIT8n7ZwUlytZkYsQMnaVGmoIZI/mVs4lijvLcR568kcnlRkYYaFKhMLq5tFDQU6+QhdTD+8WebF7ov0Ql6Q==",
"requires": {
"babel-runtime": "6.26.0",
"classnames": "2.2.5",
"lodash": "4.17.4",
"prop-types": "15.5.10"
}
}
}
},
@ -15519,12 +15557,13 @@
}
},
"semantic-ui-react": {
"version": "0.76.0",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.76.0.tgz",
"integrity": "sha512-CdiIT8n7ZwUlytZkYsQMnaVGmoIZI/mVs4lijvLcR568kcnlRkYYaFKhMLq5tFDQU6+QhdTD+8WebF7ov0Ql6Q==",
"version": "0.77.0",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.77.0.tgz",
"integrity": "sha512-lUnlpbIbMtse335kjZAw8ClulCQAUWDm77pw2yY754mL02PlTNYtZQanoGjhBFts41YHpg6ExK156J/9Ynb8IQ==",
"requires": {
"babel-runtime": "6.26.0",
"classnames": "2.2.5",
"fbjs": "0.8.16",
"lodash": "4.17.4",
"prop-types": "15.5.10"
}

View File

@ -142,14 +142,13 @@
"yargs": "6.6.0"
},
"dependencies": {
"@parity/api": "2.1.x",
"@parity/dapp-accounts": "paritytech/dapp-accounts",
"@parity/api": "~2.1.1",
"@parity/plugin-signer-account": "paritytech/plugin-signer-account",
"@parity/plugin-signer-default": "paritytech/plugin-signer-default",
"@parity/plugin-signer-hardware": "paritytech/plugin-signer-hardware",
"@parity/plugin-signer-qr": "paritytech/plugin-signer-qr",
"@parity/shared": "2.2.x",
"@parity/ui": "~3.0.4",
"@parity/shared": "~2.2.1",
"@parity/ui": "~3.0.7",
"keythereum": "1.0.2",
"lodash.flatten": "4.4.0",
"lodash.omitby": "4.6.0",

View File

@ -36,7 +36,6 @@ import Status from '../Status';
import UpgradeParity from '../UpgradeParity';
import { appLogoDark as parityLogo } from '../config';
import Store from './store';
import styles from './application.css';
const inFrame = window.parent !== window && window.parent.frames.length !== 0;
@ -54,7 +53,6 @@ class Application extends Component {
pending: PropTypes.array
}
store = new Store(this.context.api);
hwstore = HardwareStore.get(this.context.api);
upgradeStore = UpgradeStore.get(this.context.api);
@ -114,10 +112,7 @@ class Application extends Component {
return (
<div className={ styles.container }>
<Extension />
<FirstRun
onClose={ this.store.closeFirstrun }
visible={ this.store.firstrunVisible }
/>
<FirstRun />
<Snackbar />
<UpgradeParity upgradeStore={ this.upgradeStore } />
<Errors />

View File

@ -1,95 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
const OLD_LS_FIRST_RUN_KEY = 'showFirstRun';
const LS_FIRST_RUN_KEY = '_parity::showFirstRun';
export default class Store {
@observable firstrunVisible = false;
constructor (api) {
// Migrate the old key to the new one
this._migrateStore();
this._api = api;
// Show the first run the storage doesn't hold `false` value
const firstrunVisible = store.get(LS_FIRST_RUN_KEY) !== false;
// Only check accounts if we might show the first run
if (firstrunVisible) {
api.transport.once('open', () => {
this._checkAccounts();
});
} else {
this.firstrunVisible = false;
}
}
@action closeFirstrun = () => {
this.toggleFirstrun(false);
}
@action toggleFirstrun = (visible = false) => {
this.firstrunVisible = visible;
// There's no need to write to storage that the
// First Run should be visible
if (!visible) {
store.set(LS_FIRST_RUN_KEY, !!visible);
}
}
/**
* Migrate the old LocalStorage key format
* to the new one
*/
_migrateStore () {
const oldValue = store.get(OLD_LS_FIRST_RUN_KEY);
const newValue = store.get(LS_FIRST_RUN_KEY);
if (newValue === undefined && oldValue !== undefined) {
store.set(LS_FIRST_RUN_KEY, oldValue);
store.remove(OLD_LS_FIRST_RUN_KEY);
}
}
_checkAccounts () {
return Promise
.all([
this._api.parity.listVaults(),
this._api.parity.allAccountsInfo()
])
.then(([ vaults, info ]) => {
const accounts = Object.keys(info)
.filter((address) => info[address].uuid)
// In DEV mode, the empty phrase account is already added
.filter((address) => address.toLowerCase() !== '0x00a329c0648769a73afac7f9381e08fb43dbea72');
// Has accounts if any vaults or accounts
const hasAccounts = (accounts && accounts.length > 0) || (vaults && vaults.length > 0);
// Show First Run if no accounts and no vaults
this.toggleFirstrun(!hasAccounts);
})
.catch((error) => {
console.error('checkAccounts', error);
});
}
}

View File

@ -21,19 +21,16 @@ import ReactMarkdown from 'react-markdown';
import Checkbox from '@parity/ui/lib/Form/Checkbox';
import tnc from './tnc.md';
import styles from '../firstRun.css';
let tnc = '';
if (process.env.NODE_ENV !== 'test') {
tnc = require('./tnc.md');
}
export default function TnC ({ hasAccepted, onAccept }) {
return (
<div className={ styles.tnc }>
<ReactMarkdown
className={ styles.markdown }
escapeHtml={ false }
source={ tnc }
/>
<Checkbox

View File

@ -16,289 +16,56 @@
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createIdentityImg } from '@parity/api/lib/util/identity';
import { newError } from '@parity/shared/lib/redux/actions';
import Button from '@parity/ui/lib/Button';
import Portal from '@parity/ui/lib/Portal';
import { CheckIcon, DoneIcon, NextIcon, PrintIcon, ReplayIcon } from '@parity/ui/lib/Icons';
import { DoneIcon } from '@parity/ui/lib/Icons';
import { appLogoDarkNoText as parityLogo } from '../config';
import { NewAccount, AccountDetails } from '@parity/dapp-accounts/src/CreateAccount';
import print from '@parity/dapp-accounts/src/CreateAccount/print';
import recoveryPage from '@parity/dapp-accounts/src/CreateAccount/recoveryPage.ejs';
import CreateStore from '@parity/dapp-accounts/src/CreateAccount/store';
import Completed from './Completed';
import Store from './store';
import TnC from './TnC';
import Welcome from './Welcome';
const STAGE_NAMES = [
<FormattedMessage
id='firstRun.title.welcome'
defaultMessage='welcome'
/>,
<FormattedMessage
id='firstRun.title.terms'
defaultMessage='terms'
/>,
<FormattedMessage
id='firstRun.title.newAccount'
defaultMessage='new account'
/>,
<FormattedMessage
id='firstRun.title.recovery'
defaultMessage='recovery'
/>,
<FormattedMessage
id='firstRun.title.confirmation'
defaultMessage='confirmation'
/>,
<FormattedMessage
id='firstRun.title.completed'
defaultMessage='completed'
/>
];
const BUTTON_LABEL_NEXT = (
<FormattedMessage
id='firstRun.button.next'
defaultMessage='Next'
/>
);
@observer
class FirstRun extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
hasAccounts: PropTypes.bool.isRequired,
newError: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
visible: PropTypes.bool.isRequired
}
createStore = new CreateStore(this.context.api, {}, true, false);
export default class FirstRun extends Component {
state = {
stage: 0,
hasAcceptedTnc: false
}
render () {
const { visible } = this.props;
const { stage } = this.state;
store = new Store();
if (!visible) {
render () {
const { hasAcceptedTnc } = this.state;
if (!this.store.visible) {
return null;
}
return (
<Portal
buttons={ this.renderDialogActions() }
activeStep={ stage }
hideClose
steps={ STAGE_NAMES }
open
>
{ this.renderStage() }
</Portal>
);
}
renderStage () {
const { stage, hasAcceptedTnc } = this.state;
switch (stage) {
case 0:
return (
<Welcome />
);
case 1:
return (
<TnC
hasAccepted={ hasAcceptedTnc }
onAccept={ this.onAcceptTnC }
/>
);
case 2:
return (
<NewAccount
newError={ this.props.newError }
createStore={ this.createStore }
/>
);
case 3:
return (
<AccountDetails
createStore={ this.createStore }
withRequiredBackup
/>
);
case 4:
return (
<AccountDetails
createStore={ this.createStore }
isConfirming
/>
);
case 5:
return (
<Completed />
);
}
}
renderDialogActions () {
const { hasAccounts } = this.props;
const { stage, hasAcceptedTnc } = this.state;
const { canCreate, phraseBackedUpError } = this.createStore;
switch (stage) {
case 0:
return (
<Button
icon={ <NextIcon /> }
key='next'
label={ BUTTON_LABEL_NEXT }
onClick={ this.onNext }
/>
);
case 1:
return (
buttons={
<Button
disabled={ !hasAcceptedTnc }
icon={ <NextIcon /> }
key='next'
label={ BUTTON_LABEL_NEXT }
onClick={ this.onNext }
/>
);
case 2:
const buttons = [
<Button
disabled={ !canCreate }
icon={ <NextIcon /> }
key='next'
label={ BUTTON_LABEL_NEXT }
onClick={ this.onNext }
/>
];
if (hasAccounts) {
buttons.unshift(
<Button
icon={ <NextIcon /> }
key='skip'
label={
<FormattedMessage
id='firstRun.button.skip'
defaultMessage='Skip'
/>
}
onClick={ this.skipAccountCreation }
/>
);
}
return buttons;
case 3:
return [
<Button
icon={ <PrintIcon /> }
key='print'
label={
<FormattedMessage
id='firstRun.button.print'
defaultMessage='Print Phrase'
/>
}
onClick={ this.printPhrase }
/>,
<Button
disabled={ !!phraseBackedUpError }
icon={ <NextIcon /> }
key='next'
label={ BUTTON_LABEL_NEXT }
onClick={ this.onConfirmPhraseBackup }
/>
];
case 4:
return [
<Button
icon={ <ReplayIcon /> }
key='restart'
label={
<FormattedMessage
id='firstRun.button.restart'
defaultMessage='Start Over'
/>
}
onClick={ this.onStartOver }
/>,
<Button
icon={ <CheckIcon /> }
key='create'
label={
<FormattedMessage
id='firstRun.button.create'
defaultMessage='Create'
/>
}
onClick={ this.onCreate }
/>
];
case 5:
return (
<Button
icon={ <DoneIcon /> }
key='close'
label={
<FormattedMessage
id='firstRun.button.close'
defaultMessage='Close'
/>
}
onClick={ this.onClose }
key='accept'
label='Close'
onClick={ this.store.close }
/>
);
}
}
onClose = () => {
const { onClose } = this.props;
this.setState({
stage: 0
}, onClose);
}
onConfirmPhraseBackup = () => {
this.createStore.clearPhrase();
this.onNext();
}
onNext = () => {
const { stage } = this.state;
this.setState({
stage: stage + 1
});
}
onStartOver = () => {
this.setState({
stage: 2
});
}
hideClose
title={
<FormattedMessage
id='firstRun.title.termsOnly'
defaultMessage='Terms &amp; Conditions'
/>
}
open
>
<TnC
hasAccepted={ hasAcceptedTnc }
onAccept={ this.onAcceptTnC }
/>
</Portal>
);
}
onAcceptTnC = () => {
@ -306,63 +73,4 @@ class FirstRun extends Component {
hasAcceptedTnc: !this.state.hasAcceptedTnc
});
}
onCreate = () => {
this.createStore.setBusy(true);
this.createStore.computeBackupPhraseAddress()
.then(err => {
if (err) {
this.createStore.setBusy(false);
return;
}
return this.createStore.createAccount()
.then(() => {
this.createStore.clearPhrase();
this.createStore.setBusy(false);
this.onNext();
});
})
.catch((error) => {
this.createStore.setBusy(false);
this.props.newError(error);
});
}
skipAccountCreation = () => {
this.setState({ stage: this.state.stage + 3 });
}
printPhrase = () => {
const { address, phrase, name } = this.createStore;
const identity = createIdentityImg(address);
print(recoveryPage({
address,
identity,
logo: parityLogo,
name,
phrase
}));
}
}
function mapStateToProps (state) {
const { hasAccounts } = state.personal;
return {
hasAccounts
};
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
newError
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(FirstRun);

View File

@ -23,44 +23,14 @@ import FirstRun from './';
let component;
let onClose;
function createApi () {
return {};
}
function createRedux () {
return {
dispatch: sinon.stub(),
subscribe: sinon.stub(),
getState: () => {
return {
personal: {
hasAccounts: false
},
nodeStatus: {
isTest: false
}
};
}
};
}
function render (props = { visible: true }) {
onClose = sinon.stub();
component = shallow(
<FirstRun
{ ...props }
onClose={ onClose }
/>,
{
context: {
store: createRedux()
}
}
).find('FirstRun').shallow({
context: {
api: createApi()
}
});
/>
);
return component;
}

42
js/src/FirstRun/store.js Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
const LS_FIRST_RUN_KEY = '_parity::showFirstRun';
export default class Store {
@observable visible = false;
constructor (api) {
this.toggle(store.get(LS_FIRST_RUN_KEY) !== false);
}
@action close = () => {
this.toggle(false);
}
@action toggle = (visible = false) => {
this.visible = visible;
// There's no need to write to storage that the
// First Run should be visible
if (!visible) {
store.set(LS_FIRST_RUN_KEY, !!visible);
}
}
}