diff --git a/src/Accounts/Accounts.js b/src/Accounts/Accounts.js index 6decf0d6c615affb540c0455524dcc2d7584b74c..b48d4cad187dcb80d50f54059e1fd25f8a5781ab 100644 --- a/src/Accounts/Accounts.js +++ b/src/Accounts/Accounts.js @@ -3,103 +3,22 @@ // // SPDX-License-Identifier: MIT -import React, { Component } from 'react'; -import { accountsInfo$ } from '@parity/light.js'; -import Blockies from 'react-blockies'; -import { inject, observer } from 'mobx-react'; +import React, { PureComponent } from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; -import Health from '../Health'; -import CreateAccount from './CreateAccount/CreateAccount'; -import light from '../hoc'; - -@light({ - accountsInfo: accountsInfo$ -}) -@inject('createAccountStore', 'parityStore') -@observer -class Accounts extends Component { - handleClick = ({ currentTarget: { dataset: { address } } }) => { - const { history, parityStore: { api } } = this.props; - // Set default account to the clicked one, and go to Tokens on complete - this.subscription = api.parity - .setNewDappsDefaultAddress(address) - .then(() => history.push('/tokens')); - }; - - handleCreateAccount = () => { - this.props.createAccountStore.setIsImporting(false); - this.props.history.push('/accounts/new'); - }; +import AccountsList from './AccountsList'; +import CreateAccount from './CreateAccount'; +class Accounts extends PureComponent { render () { return ( - + ); } - - // TODO We can put this into a separate Component - renderAccounts = () => { - const { accountsInfo } = this.props; - - return ( -
- - -
-
- {accountsInfo - ?
    - {Object.keys(accountsInfo).map(address => -
  • -
    -
    - -
    -
    -
    - {accountsInfo[address].name} -
    -
    - {address} -
    -
    -
    -
  • - )} -
- :
-

Loading…

-
} -
-
- - -
- ); - }; } export default Accounts; diff --git a/src/Accounts/AccountsList/AccountsList.js b/src/Accounts/AccountsList/AccountsList.js new file mode 100644 index 0000000000000000000000000000000000000000..a0e1eb01d9e0d7eefb76248956be247ddc190de9 --- /dev/null +++ b/src/Accounts/AccountsList/AccountsList.js @@ -0,0 +1,99 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +import React, { Component } from 'react'; +import { accountsInfo$ } from '@parity/light.js'; +import Blockies from 'react-blockies'; +import { inject, observer } from 'mobx-react'; + +import Health from '../../Health'; +import light from '../../hoc'; + +@light({ + accountsInfo: accountsInfo$ +}) +@inject('createAccountStore', 'parityStore') +@observer +class AccountsList extends Component { + handleClick = ({ + currentTarget: { + dataset: { address } + } + }) => { + const { + history, + parityStore: { api } + } = this.props; + // Set default account to the clicked one, and go to Tokens on complete + this.subscription = api.parity + .setNewDappsDefaultAddress(address) + .then(() => history.push('/tokens')); + }; + + handleCreateAccount = () => { + this.props.createAccountStore.setIsImporting(false); + this.props.history.push('/accounts/new'); + }; + + render () { + const { accountsInfo } = this.props; + + return ( +
+ + +
+
+ {accountsInfo ? ( +
    + {Object.keys(accountsInfo).map(address => ( +
  • +
    +
    + +
    +
    +
    + {accountsInfo[address].name} +
    +
    {address}
    +
    +
    +
  • + ))} +
+ ) : ( +
+

Loading…

+
+ )} +
+
+ + +
+ ); + } +} + +export default AccountsList; diff --git a/src/Accounts/AccountsList/index.js b/src/Accounts/AccountsList/index.js new file mode 100644 index 0000000000000000000000000000000000000000..0cc9257199d7851c73476575509229b4d8f5ecb2 --- /dev/null +++ b/src/Accounts/AccountsList/index.js @@ -0,0 +1,8 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +import AccountsList from './AccountsList'; + +export default AccountsList; diff --git a/src/Accounts/CreateAccount/AccountConfirm/AccountConfirm.js b/src/Accounts/CreateAccount/AccountConfirm/AccountConfirm.js index 61981dbeb94fe3f99f7110ca4a022defb4b4363a..2f6793cf6f26e96e4f82eb76baf34b4fde315d95 100644 --- a/src/Accounts/CreateAccount/AccountConfirm/AccountConfirm.js +++ b/src/Accounts/CreateAccount/AccountConfirm/AccountConfirm.js @@ -8,11 +8,22 @@ import { inject, observer } from 'mobx-react'; import CreateAccountHeader from '../CreateAccountHeader'; -@inject('createAccountStore') +@inject('createAccountStore', 'onboardingStore') @observer class AccountConfirm extends Component { handleSubmit = () => { - const { createAccountStore: { saveAccountToParity }, history } = this.props; + const { + createAccountStore: { saveAccountToParity }, + history, + onboardingStore + } = this.props; + + // If we were onboarding, set isFirstRun to false, so that we quit + // onboarding. + if (onboardingStore.isFirstRun) { + onboardingStore.setIsFirstRun(false); + } + saveAccountToParity().then(() => history.push('/accounts')); }; diff --git a/src/Accounts/CreateAccount/AccountName/AccountName.js b/src/Accounts/CreateAccount/AccountName/AccountName.js index 6d9b7fdee52630637d7fbabb68671638156b19c4..fe20e3bac5a6c2b250d25b1cd3082aaf4542850e 100644 --- a/src/Accounts/CreateAccount/AccountName/AccountName.js +++ b/src/Accounts/CreateAccount/AccountName/AccountName.js @@ -13,7 +13,11 @@ import CreateAccountHeader from '../CreateAccountHeader'; @observer class AccountName extends Component { componentDidMount () { - this.props.createAccountStore.generateNewAccount(); + const { createAccountStore } = this.props; + // Generate a new public address if there's none yet + if (!createAccountStore.address) { + createAccountStore.generateNewAccount(); + } } handleChangeName = ({ target: { value } }) => @@ -28,12 +32,12 @@ class AccountName extends Component { return (
- {address && + {address && (
- {!isImport && + {!isImport && (
-
} +
+ )}

Please give this account a name:

@@ -55,17 +60,20 @@ class AccountName extends Component { />
- } + + )} ); } diff --git a/src/Accounts/CreateAccount/CreateAccount.js b/src/Accounts/CreateAccount/CreateAccount.js index 88e4d835e9159230913f48c63fa6a3005e36957e..2bc9fd81a780be0d6cc4498703fe93565b61b063 100644 --- a/src/Accounts/CreateAccount/CreateAccount.js +++ b/src/Accounts/CreateAccount/CreateAccount.js @@ -4,7 +4,7 @@ // SPDX-License-Identifier: MIT import React, { Component } from 'react'; -import { Link, Route } from 'react-router-dom'; +import { Route } from 'react-router-dom'; import { inject, observer } from 'mobx-react'; import memoize from 'lodash/memoize'; @@ -33,20 +33,24 @@ class CreateAccount extends Component { ]; }); - handleImportAccount = () => { - this.props.createAccountStore.setIsImporting(true); + handleCreateAccount = () => { + this.props.createAccountStore.setIsImporting(false); this.props.history.push('/accounts/new'); }; - handleCreateAccount = () => { - this.props.createAccountStore.setIsImporting(false); + handleGoBack = () => this.props.history.goBack(); + + handleImportAccount = () => { + this.props.createAccountStore.setIsImporting(true); this.props.history.push('/accounts/new'); }; render () { const { createAccountStore: { isImport }, - match: { params: { step } } // Current step + match: { + params: { step } + } // Current step } = this.props; // Get all the steps of our account process @@ -56,18 +60,16 @@ class CreateAccount extends Component {
- {Steps.map((StepComponent, index) => + {Steps.map((StepComponent, index) => ( - )} + ))}
diff --git a/src/App/App.js b/src/App/App.js index 87f81febe99e228ed78c725d90c899160d7cd77a..d519769601fe196535ffefc94f4edb72459a1e58 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -14,6 +14,7 @@ import { import { inject, observer } from 'mobx-react'; import Accounts from '../Accounts'; +import Onboarding from '../Onboarding'; import Overlay from '../Overlay'; import Receive from '../Receive'; import Send from '../Send'; @@ -28,16 +29,10 @@ import './App.css'; const Router = process.env.NODE_ENV === 'production' ? MemoryRouter : BrowserRouter; -@inject('healthStore') +@inject('healthStore', 'onboardingStore') @observer class App extends Component { render () { - const { - healthStore: { - health: { status } - } - } = this.props; - return (
@@ -47,28 +42,50 @@ class App extends Component {
-
- {status === STATUS.GOOD ? ( - - {/* Change homepage on the next line */} - - - - - - - - - - ) : ( - - )} -
+
{this.renderScreen()}
); } + + /** + * Decide which screen to render. + */ + renderScreen () { + const { + onboardingStore: { isOnboarding }, + healthStore: { + health: { status } + } + } = this.props; + + // If we are onboarding, then never show the Overlay. On the other hand, if + // we're not onboarding, show the Overlay whenever we have an issue. + if (!isOnboarding && status !== STATUS.GOOD) { + return ; + } + + return ( + + {/* We redirect to Onboarding if necessary, or by default to our + homepage which is Tokens */} + + + + + + + + + + + ); + } } export default App; diff --git a/src/Onboarding/Onboarding.js b/src/Onboarding/Onboarding.js new file mode 100644 index 0000000000000000000000000000000000000000..40b8d2cc7d5a29eda888d60b0b0d246c4a5258bf --- /dev/null +++ b/src/Onboarding/Onboarding.js @@ -0,0 +1,40 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; +import { Link } from 'react-router-dom'; + +import Health from '../Health'; + +@inject('onboardingStore') +@observer +class Onboarding extends Component { + handleFirstRun = () => { + // Not first run anymore after clicking Start + this.props.onboardingStore.setIsFirstRun(false); + }; + + render () { + const { + onboardingStore: { hasAccounts } + } = this.props; + return ( +
+ This is the onboarding page.
+ {hasAccounts ? ( + + Start + + ) : ( + Create account + )} + +
+ ); + } +} + +export default Onboarding; diff --git a/src/Onboarding/index.js b/src/Onboarding/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ac178853b60dadff3f452a70b3874cae914d2da7 --- /dev/null +++ b/src/Onboarding/index.js @@ -0,0 +1,8 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +import Onboarding from './Onboarding'; + +export default Onboarding; diff --git a/src/stores/index.js b/src/stores/index.js index d2346d8aa89dccc2693799b4812c4127dc15620c..16f1178c62a0fb1744e24266637f17bfcc52f865 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -5,6 +5,7 @@ import createAccountStore from './createAccountStore'; import healthStore from './healthStore'; +import onboardingStore from './onboardingStore'; import parityStore from './parityStore'; import signerStore from './signerStore'; import tokensStore from './tokensStore'; @@ -12,6 +13,7 @@ import tokensStore from './tokensStore'; export default { createAccountStore, healthStore, + onboardingStore, parityStore, signerStore, tokensStore diff --git a/src/stores/onboardingStore.js b/src/stores/onboardingStore.js new file mode 100644 index 0000000000000000000000000000000000000000..fc878ecd0776eba963fff0271b9ded565c7e2b8b --- /dev/null +++ b/src/stores/onboardingStore.js @@ -0,0 +1,62 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +import { accountsInfo$ } from '@parity/light.js'; +import { action, computed, observable } from 'mobx'; +import { map } from 'rxjs/operators'; +import store from 'store'; + +import LS_PREFIX from './utils/lsPrefix'; + +const LS_KEY = `${LS_PREFIX}::firstRun`; + +class OnboardingStore { + @observable hasAccounts; // If the user has at least 1 account or not + @observable isFirstRun; // If it's the 1st time the user is running the app + + constructor () { + const isFirstRun = store.get(LS_KEY); + + if (isFirstRun === undefined) { + // Set store property to true. + this.setIsFirstRun(true); + } else { + this.setIsFirstRun(isFirstRun); + } + + accountsInfo$() + .pipe(map(accounts => Object.keys(accounts).length > 0)) + .subscribe(this.setHasAccounts); + } + + /** + * We show the onboarding process if: + * - either it's the 1st time the user runs this app + * - or the user has 0 account + */ + @computed + get isOnboarding () { + // If either of the two is undefined, then it means we're still fetching. + // This doesn't count as onboarding. + return this.hasAccounts === undefined || this.isFirstRun === undefined + ? false + : !this.hasAccounts || this.isFirstRun; + } + + @action + setHasAccounts = hasAccounts => { + this.hasAccounts = hasAccounts; + }; + + @action + setIsFirstRun = isFirstRun => { + this.isFirstRun = isFirstRun; + this.updateLS(); + }; + + updateLS = () => store.set(LS_KEY, this.isFirstRun); +} + +export default new OnboardingStore(); diff --git a/src/stores/parityStore.js b/src/stores/parityStore.js index daf392d429a6b6ec48af081c6e891f34df901c83..b5c7c9e5457e48a49cab6ef060815091128325d0 100644 --- a/src/stores/parityStore.js +++ b/src/stores/parityStore.js @@ -9,10 +9,11 @@ import isElectron from 'is-electron'; import light from '@parity/light.js'; import store from 'store'; +import LS_PREFIX from './utils/lsPrefix'; + const electron = isElectron() ? window.require('electron') : null; -const LS_PREFIX = '__paritylight::'; -const LS_KEY = `${LS_PREFIX}secureToken`; +const LS_KEY = `${LS_PREFIX}::secureToken`; class ParityStore { @observable downloadProgress = 0; diff --git a/src/stores/signerStore.js b/src/stores/signerStore.js index 785cca7a3ffc5a04b8e18a34325f3d2b37c6406e..dcf8bc39bb34c91d7d0c413e6bd7408c3e457ef0 100644 --- a/src/stores/signerStore.js +++ b/src/stores/signerStore.js @@ -19,7 +19,7 @@ class SignerStore { // TODO This .on() is not working, so we poll every second // this.api.on('connected', this.subscribePending); this.interval = setInterval(() => { - if (parityStore.isApiConnected) { + if (parityStore.isApiConnected && this.api) { this.subscribePending(); clearInterval(this.interval); } diff --git a/src/stores/tokensStore.js b/src/stores/tokensStore.js index 6778956a6db31e7cf306dc7e4f248fdf947a946c..562b0bcc90b6dc8057c36e2c268cb6add9e7a8c1 100644 --- a/src/stores/tokensStore.js +++ b/src/stores/tokensStore.js @@ -9,20 +9,19 @@ import { combineLatest } from 'rxjs'; import store from 'store'; import ethereumIcon from '../assets/img/tokens/ethereum.png'; +import LS_PREFIX from './utils/lsPrefix'; -const LS_PREFIX = '__paritylight::tokens'; +const LS_KEY = `${LS_PREFIX}::tokens`; class TokensStore { @observable tokens = new Map(); constructor () { - combineLatest( - chainName$(), - defaultAccount$() - ).subscribe(([chainName, defaultAccount]) => - // Refetch token from localStorage everytime we have a new chainName - // (shouldn't happen) or the user selects a new account - this.fetchTokensFromDb(chainName, defaultAccount) + combineLatest(chainName$(), defaultAccount$()).subscribe( + ([chainName, defaultAccount]) => + // Refetch token from localStorage everytime we have a new chainName + // (shouldn't happen) or the user selects a new account + this.fetchTokensFromDb(chainName, defaultAccount) ); } @@ -36,7 +35,7 @@ class TokensStore { fetchTokensFromDb = async (chainName, defaultAccount) => { // Set the localStorage key, we have one key per chain per account, in this // format: __paritylight::tokens::0x123::kovan - this.lsKey = `${LS_PREFIX}::${defaultAccount}::${chainName}`; + this.lsKey = `${LS_KEY}::${defaultAccount}::${chainName}`; // Now we fetch the tokens from the localStorage const tokens = store.get(this.lsKey); diff --git a/src/stores/utils/lsPrefix.js b/src/stores/utils/lsPrefix.js new file mode 100644 index 0000000000000000000000000000000000000000..52c6efcfb6a209c12e87112c56fd231dcc0ce0c5 --- /dev/null +++ b/src/stores/utils/lsPrefix.js @@ -0,0 +1,7 @@ +// Copyright 2015-2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. +// +// SPDX-License-Identifier: MIT + +// All keys in localStorage are prefixed with: +export default '__paritylight';