diff --git a/README.md b/README.md index 87d45df8..ae08c10f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# MyCrypto Beta RC (VISIT [MyCryptoHQ/mycrypto.com](https://github.com/MyCryptoHQ/mycrypto.com) for the current site)
Just looking to download? Grab our [latest release](https://github.com/MyCryptoHQ/MyCrypto/releases) +# MyCrypto Web & Desktop Apps [![Greenkeeper badge](https://badges.greenkeeper.io/MyCryptoHq/MyCrypto.svg)](https://greenkeeper.io/) [![Coverage Status](https://coveralls.io/repos/github/MyCryptoHQ/MyCrypto/badge.svg?branch=develop)](https://coveralls.io/github/MyCryptoHQ/MyCrypto?branch=develop) +* **Just looking to download?** Grab our [latest release](https://github.com/MyCryptoHQ/MyCrypto/releases). +* **Looking for the old site?** Check out [https://legacy.mycrypto.com](https://legacy.mycrypto.com) or the source at [MyCryptoHQ/mycrypto.com](https://github.com/MyCryptoHQ/mycrypto.com) + ## Running the App This codebase targets Node 8.9.4 (LTS). After `npm install`ing all dependencies (You may be required to install additional system dependencies, due to some node modules relying on them) you can run various commands depending on what you want to do: @@ -132,3 +135,4 @@ npm run test:int Cross browser testing and debugging provided by the very lovely team at BrowserStack. + diff --git a/common/Root.tsx b/common/Root.tsx index 0fe992d7..59dba1a1 100644 --- a/common/Root.tsx +++ b/common/Root.tsx @@ -15,8 +15,9 @@ import ErrorScreen from 'components/ErrorScreen'; import PageNotFound from 'components/PageNotFound'; import LogOutPrompt from 'components/LogOutPrompt'; import QrSignerModal from 'containers/QrSignerModal'; +import OnboardModal from 'containers/OnboardModal'; +import WelcomeModal from 'components/WelcomeModal'; import NewAppReleaseModal from 'components/NewAppReleaseModal'; -import { TitleBar } from 'components/ui'; import { Store } from 'redux'; import { pollOfflineStatus, TPollOfflineStatus } from 'actions/config'; import { AppState } from 'reducers'; @@ -104,12 +105,17 @@ class RootClass extends Component { - {process.env.BUILD_ELECTRON && } {routes} {process.env.BUILD_ELECTRON && } + {!process.env.DOWNLOADABLE_BUILD && ( + + + {!process.env.BUILD_ELECTRON && } + + )} diff --git a/common/assets/images/link-preview.png b/common/assets/images/link-preview.png new file mode 100644 index 00000000..8cffb12f Binary files /dev/null and b/common/assets/images/link-preview.png differ diff --git a/common/components/AddressField.tsx b/common/components/AddressField.tsx index 244bd713..7bd34e87 100644 --- a/common/components/AddressField.tsx +++ b/common/components/AddressField.tsx @@ -21,7 +21,7 @@ export const AddressField: React.SFC = ({ isReadOnly, isSelfAddress, isCh {translate(isSelfAddress ? 'X_ADDRESS' : 'SEND_ADDR')} = ({ @@ -106,14 +103,19 @@ export default class AddCustomTokenForm extends React.PureComponent { - prev[tk.symbol] = true; - return prev; - }, - {} as IGenerateSymbolLookup - ); + private generateSymbolLookup() { + return this.tknArrToMap('symbol'); + } + + private generateAddressMap() { + return this.tknArrToMap('address'); + } + + private tknArrToMap(key: Exclude) { + const tokens = this.props.allTokens; + return tokens.reduce<{ [k: string]: boolean }>((prev, tk) => { + prev[tk[key]] = true; + return prev; + }, {}); } } diff --git a/common/components/BalanceSidebar/TokenBalances/Balances.tsx b/common/components/BalanceSidebar/TokenBalances/Balances.tsx index d5f448da..c21f35d1 100644 --- a/common/components/BalanceSidebar/TokenBalances/Balances.tsx +++ b/common/components/BalanceSidebar/TokenBalances/Balances.tsx @@ -20,7 +20,7 @@ interface TrackedTokens { } interface State { - trackedTokens: { [symbol: string]: boolean }; + trackedTokens: TrackedTokens; showCustomTokenForm: boolean; } export default class TokenBalances extends React.PureComponent { @@ -29,10 +29,10 @@ export default class TokenBalances extends React.PureComponent { showCustomTokenForm: false }; - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { if (nextProps.tokenBalances !== this.props.tokenBalances) { const trackedTokens = nextProps.tokenBalances.reduce((prev, t) => { - prev[t.symbol] = !t.balance.isZero(); + prev[t.symbol] = !t.balance.isZero() || t.custom; return prev; }, {}); this.setState({ trackedTokens }); @@ -45,7 +45,7 @@ export default class TokenBalances extends React.PureComponent { let bottom; let help; - if (tokenBalances.length && !hasSavedWalletTokens) { + if (tokenBalances.length && !hasSavedWalletTokens && !this.onlyCustomTokens()) { help = 'Select which tokens you would like to keep track of'; bottom = (
@@ -134,6 +134,24 @@ export default class TokenBalances extends React.PureComponent { }); }; + /** + * + * @description Checks if all currently tracked tokens are custom + * @private + * @returns + * @memberof TokenBalances + */ + private onlyCustomTokens() { + const tokenMap = this.props.tokenBalances.reduce<{ [key: string]: TokenBalance }>( + (acc, cur) => ({ ...acc, [cur.symbol]: cur }), + {} + ); + + return Object.keys(this.state.trackedTokens).reduce( + (prev, tokenName) => tokenMap[tokenName].custom && prev, + true + ); + } private addCustomToken = (token: Token) => { this.props.onAddCustomToken(token); this.setState({ showCustomTokenForm: false }); diff --git a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx index f93cdff0..8fccce3b 100644 --- a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx +++ b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx @@ -30,11 +30,14 @@ export default class TokenRow extends React.PureComponent { return ( - {this.props.toggleTracked && ( - - - - )} + {/* Only allow to toggle tracking on non custom tokens + because the user can just remove the custom token instead */} + {!this.props.custom && + this.props.toggleTracked && ( + + + + )} { - public state = { - hasAcknowledged: !!localStorage.getItem(LS_KEY), - isFading: false - }; - - public render() { - if (this.state.hasAcknowledged) { - return null; - } - - const isFading = this.state.isFading ? 'is-fading' : ''; - - return ( -
-
-

Welcome to the New MyCrypto Beta Release Candidate!

-

- You are about to use the new MyCrypto Beta Release Candidate. Although this is a release - candidate for production, we encourage caution while using this unreleased version of - MyCrypto. -

-

We hope to move this version of MyCrypto into production in the near future!

-

- Feedback and bug reports are greatly appreciated. You can file issues on our{' '} - - GitHub repository - {' '} - or join our Discord server to discuss the - beta. -

-

Are you sure you would like to continue?

- -
- - -
-
-
- ); - } - - private doContinue = () => { - localStorage.setItem(LS_KEY, 'true'); - this.setState({ isFading: true }); - - setTimeout(() => { - this.setState({ hasAcknowledged: true }); - }, 1000); - }; - - private reject = () => { - window.location.assign('https://mycrypto.com'); - }; -} diff --git a/common/components/ConfirmationModal/components/Body/components/Details.tsx b/common/components/ConfirmationModal/components/Body/components/Details.tsx index 0a3431b6..3760e40e 100644 --- a/common/components/ConfirmationModal/components/Body/components/Details.tsx +++ b/common/components/ConfirmationModal/components/Body/components/Details.tsx @@ -20,7 +20,12 @@ class DetailsClass extends Component {
{ this.setAddressState(this.props); } - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { if (this.props.wallet !== nextProps.wallet) { this.setAddressState(nextProps); } diff --git a/common/components/CustomNodeModal/CustomNodeModal.tsx b/common/components/CustomNodeModal/CustomNodeModal.tsx index 1eb9235c..fbfe600a 100644 --- a/common/components/CustomNodeModal/CustomNodeModal.tsx +++ b/common/components/CustomNodeModal/CustomNodeModal.tsx @@ -117,7 +117,7 @@ class CustomNodeModal extends React.Component {
- - ); diff --git a/common/components/GasLimitField.tsx b/common/components/GasLimitField.tsx index 34c6857b..ec63952b 100644 --- a/common/components/GasLimitField.tsx +++ b/common/components/GasLimitField.tsx @@ -30,7 +30,7 @@ export const GasLimitField: React.SFC = ({ /> } } - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { if (nextProps.privateKey !== this.props.privateKey) { this.setState({ privateKey: nextProps.privateKey || '' }); } diff --git a/common/components/Header/components/OnlineStatus.scss b/common/components/Header/components/OnlineStatus.scss index 7a3acbc0..c92840f1 100644 --- a/common/components/Header/components/OnlineStatus.scss +++ b/common/components/Header/components/OnlineStatus.scss @@ -12,7 +12,6 @@ .OnlineStatus { position: relative; - top: -2px; width: 12px; height: 12px; text-align: center; diff --git a/common/components/Header/components/OnlineStatus.tsx b/common/components/Header/components/OnlineStatus.tsx index d36e3090..b7dba55d 100644 --- a/common/components/Header/components/OnlineStatus.tsx +++ b/common/components/Header/components/OnlineStatus.tsx @@ -7,8 +7,8 @@ interface Props { } const OnlineStatus: React.SFC = ({ isOffline }) => ( -
- {isOffline ? 'Offline' : 'Online'} +
+ {isOffline ? 'Offline' : 'Online'}
); diff --git a/common/components/NewAppReleaseModal.tsx b/common/components/NewAppReleaseModal.tsx index 0f7611e6..2030365e 100644 --- a/common/components/NewAppReleaseModal.tsx +++ b/common/components/NewAppReleaseModal.tsx @@ -1,27 +1,12 @@ import React from 'react'; import translate, { translateRaw } from 'translations'; import Modal, { IButton } from 'components/ui/Modal'; -import { VERSION_RC } from 'config'; -import { isNewerVersion } from 'utils/helpers'; - -interface IGitHubRelease { - tag_name: string; -} - -function getLatestGitHubRelease(): Promise { - return fetch('https://api.github.com/repos/MyCryptoHQ/MyCrypto/releases/latest', { - method: 'GET', - mode: 'cors', - headers: { - 'content-type': 'application/json; charset=utf-8' - } - }) - .then(res => res.json()) - .then(data => data as IGitHubRelease); -} +import { getLatestElectronRelease } from 'utils/versioning'; +import { VERSION } from 'config/data'; interface State { isOpen: boolean; + newRelease?: string; } export default class NewAppReleaseModal extends React.Component<{}, State> { @@ -31,10 +16,9 @@ export default class NewAppReleaseModal extends React.Component<{}, State> { public async componentDidMount() { try { - const release = await getLatestGitHubRelease(); - // TODO: Use VERSION once done with release candidates - if (isNewerVersion(VERSION_RC, release.tag_name)) { - this.setState({ isOpen: true }); + const newRelease = await getLatestElectronRelease(); + if (newRelease) { + this.setState({ isOpen: true, newRelease }); } } catch (err) { console.error('Failed to fetch latest release from GitHub:', err); @@ -64,11 +48,22 @@ export default class NewAppReleaseModal extends React.Component<{}, State> { handleClose={this.close} maxWidth={520} > -
{translateRaw('APP_UPDATE_BODY')}
+

+ {translateRaw('APP_UPDATE_BODY')} {this.versionCompareStr()} +

); } + private versionCompareStr() { + return ( + <> +
Current Version: {VERSION}
+
New Version: {this.state.newRelease}
+ + ); + } + private close = () => { this.setState({ isOpen: false }); }; diff --git a/common/components/NonceField.tsx b/common/components/NonceField.tsx index ef501f07..ccffae9b 100644 --- a/common/components/NonceField.tsx +++ b/common/components/NonceField.tsx @@ -42,7 +42,8 @@ class NonceField extends React.Component { />
{ ); } - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { if (nextProps.transactionBroadcasted && this.state.showModal) { this.closeModal(); } diff --git a/common/components/SubTabs/index.tsx b/common/components/SubTabs/index.tsx index 40ccb23b..c9819a0a 100644 --- a/common/components/SubTabs/index.tsx +++ b/common/components/SubTabs/index.tsx @@ -39,7 +39,7 @@ export default class SubTabs extends React.PureComponent { window.removeEventListener('resize', this.handleResize); } - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { // When new tabs come in, we'll need to uncollapse so that they can // be measured and collapsed again, if needed. if (this.props.tabs !== nextProps.tabs) { diff --git a/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx b/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx index 993fc0d9..da1bfff3 100644 --- a/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx +++ b/common/components/TXMetaDataPanel/TXMetaDataPanel.tsx @@ -76,7 +76,7 @@ class TXMetaDataPanel extends React.Component { } } - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { if ( (this.props.offline && !nextProps.offline) || this.props.network.unit !== nextProps.network.unit diff --git a/common/components/TXMetaDataPanel/components/AdvancedGas.tsx b/common/components/TXMetaDataPanel/components/AdvancedGas.tsx index d786b9ba..7ca48c60 100644 --- a/common/components/TXMetaDataPanel/components/AdvancedGas.tsx +++ b/common/components/TXMetaDataPanel/components/AdvancedGas.tsx @@ -9,7 +9,6 @@ import { NonceField, GasLimitField, DataField } from 'components'; import { connect } from 'react-redux'; import { getAutoGasLimitEnabled } from 'selectors/config'; import { isValidGasPrice } from 'selectors/transaction'; -import { sanitizeNumericalInput } from 'libs/values'; import { Input } from 'components/ui'; import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling'; import { getScheduleGasPrice, getTimeBounty } from 'selectors/schedule'; @@ -83,9 +82,11 @@ class AdvancedGas extends React.Component {
{translateRaw('OFFLINE_STEP2_LABEL_3')} (gwei)
+ {/*We leave type as string instead of number, because things such as multiple decimals + or invalid exponent notation does not fire the onchange handler + so the component will not display as invalid for such things */} { private handleGasPriceChange = (ev: React.FormEvent) => { const { value } = ev.currentTarget; - this.props.inputGasPrice(sanitizeNumericalInput(value)); + this.props.inputGasPrice(value); }; private handleToggleAutoGasLimit = (_: React.FormEvent) => { diff --git a/common/components/TXMetaDataPanel/components/FeeSummary.tsx b/common/components/TXMetaDataPanel/components/FeeSummary.tsx index fe6ac65a..5165af52 100644 --- a/common/components/TXMetaDataPanel/components/FeeSummary.tsx +++ b/common/components/TXMetaDataPanel/components/FeeSummary.tsx @@ -54,6 +54,9 @@ class FeeSummary extends React.Component { scheduleGasLimit } = this.props; + if (!gasPrice.value || gasPrice.value.eqn(0) || !gasLimit.value || gasLimit.value.eqn(0)) { + return null; + } if (isGasEstimating) { return (
diff --git a/common/components/TXMetaDataPanel/components/SimpleGas.tsx b/common/components/TXMetaDataPanel/components/SimpleGas.tsx index e386af59..84d065d0 100644 --- a/common/components/TXMetaDataPanel/components/SimpleGas.tsx +++ b/common/components/TXMetaDataPanel/components/SimpleGas.tsx @@ -57,7 +57,7 @@ class SimpleGas extends React.Component { this.props.fetchGasEstimates(); } - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { if (!this.state.hasSetRecommendedGasPrice && nextProps.gasEstimates) { this.setState({ hasSetRecommendedGasPrice: true }); this.props.setGasPrice(nextProps.gasEstimates.fast.toString()); diff --git a/common/components/TogglablePassword.tsx b/common/components/TogglablePassword.tsx index d5871577..4c2aa657 100644 --- a/common/components/TogglablePassword.tsx +++ b/common/components/TogglablePassword.tsx @@ -40,7 +40,7 @@ export default class TogglablePassword extends React.PureComponent isVisible: !!this.props.isVisible }; - public componentWillReceiveProps(nextProps: Props) { + public UNSAFE_componentWillReceiveProps(nextProps: Props) { if (this.props.isVisible !== nextProps.isVisible) { this.setState({ isVisible: !!nextProps.isVisible }); } @@ -69,7 +69,8 @@ export default class TogglablePassword extends React.PureComponent
{isTextareaWhenVisible && isVisible ? (