Merge pull request #1781 from MyCryptoHQ/develop

Release 1.0.0
This commit is contained in:
Daniel Ternyak 2018-05-14 13:21:17 -05:00 committed by GitHub
commit 16232d6afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
122 changed files with 1514 additions and 1234 deletions

View File

@ -1,8 +1,11 @@
# MyCrypto Beta RC (VISIT [MyCryptoHQ/mycrypto.com](https://github.com/MyCryptoHQ/mycrypto.com) for the current site)<br/>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
</a>
Cross browser testing and debugging provided by the very lovely team at BrowserStack.

View File

@ -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<Props, State> {
<Provider store={store} key={Math.random()}>
<Router key={Math.random()}>
<React.Fragment>
{process.env.BUILD_ELECTRON && <TitleBar />}
{routes}
<LegacyRoutes />
<LogOutPrompt />
<QrSignerModal />
{process.env.BUILD_ELECTRON && <NewAppReleaseModal />}
{!process.env.DOWNLOADABLE_BUILD && (
<React.Fragment>
<OnboardModal />
{!process.env.BUILD_ELECTRON && <WelcomeModal />}
</React.Fragment>
)}
</React.Fragment>
</Router>
</Provider>

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -21,7 +21,7 @@ export const AddressField: React.SFC<Props> = ({ isReadOnly, isSelfAddress, isCh
{translate(isSelfAddress ? 'X_ADDRESS' : 'SEND_ADDR')}
</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={isCheckSummed ? toChecksumAddress(currentTo.raw) : currentTo.raw}
placeholder={donationAddressMap.ETH}

View File

@ -23,9 +23,7 @@ export const AmountField: React.SFC<Props> = ({
<label className="AmountField-group input-group input-group-inline">
<div className="input-group-header">{translate('SEND_AMOUNT_SHORT')}</div>
<Input
className={`input-group-input ${
isAmountValid(raw, customValidator, isValid) ? '' : 'invalid'
}`}
isValid={isAmountValid(raw, customValidator, isValid)}
type="number"
placeholder="1"
value={raw}

View File

@ -88,7 +88,7 @@ class EquivalentValues extends React.Component<Props, State> {
};
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
const { balance, tokenBalances, isOffline, network } = this.props;
if (
nextProps.balance !== balance ||

View File

@ -16,8 +16,13 @@ interface IGenerateSymbolLookup {
[tokenSymbol: string]: boolean;
}
interface IGenerateAddressLookup {
[address: string]: boolean;
}
interface State {
tokenSymbolLookup: IGenerateSymbolLookup;
tokenAddressLookup: IGenerateAddressLookup;
address: string;
symbol: string;
decimal: string;
@ -25,20 +30,13 @@ interface State {
export default class AddCustomTokenForm extends React.PureComponent<Props, State> {
public state: State = {
tokenSymbolLookup: {},
tokenSymbolLookup: this.generateSymbolLookup(),
tokenAddressLookup: this.generateAddressMap(),
address: '',
symbol: '',
decimal: ''
};
constructor(props: Props) {
super(props);
this.state = {
...this.state,
tokenSymbolLookup: this.generateSymbolLookup(props.allTokens)
};
}
public render() {
const { address, symbol, decimal } = this.state;
const errors = this.getErrors();
@ -68,15 +66,14 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
<label className="AddCustom-field form-group" key={field.name}>
<div className="input-group-header">{field.label}</div>
<Input
className={`${
errors[field.name] ? 'invalid' : field.value ? 'valid' : ''
} input-group-input-small`}
isValid={!errors[field.name]}
className="input-group-input-small"
type="text"
name={field.name}
value={field.value}
onChange={this.onFieldChange}
/>
{typeof errors[field.name] === 'string' && (
{errors[field.name] && (
<div className="AddCustom-field-error">{errors[field.name]}</div>
)}
</label>
@ -106,14 +103,19 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
public getErrors() {
const { address, symbol, decimal } = this.state;
const errors: { [key: string]: boolean | string } = {};
const errors: { [key: string]: string } = {};
// Formatting errors
if (decimal && !isPositiveIntegerOrZero(parseInt(decimal, 10))) {
errors.decimal = true;
if (decimal && !isPositiveIntegerOrZero(Number(decimal))) {
errors.decimal = 'Invalid decimal';
}
if (address && !isValidETHAddress(address)) {
errors.address = true;
if (address) {
if (!isValidETHAddress(address)) {
errors.address = 'Not a valid address';
}
if (this.state.tokenAddressLookup[address]) {
errors.address = 'A token with this address already exists';
}
}
// Message errors
@ -146,13 +148,19 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
this.props.onSave({ address, symbol, decimal: parseInt(decimal, 10) });
};
private generateSymbolLookup(tokens: Token[]) {
return tokens.reduce(
(prev, tk) => {
prev[tk.symbol] = true;
return prev;
},
{} as IGenerateSymbolLookup
);
private generateSymbolLookup() {
return this.tknArrToMap('symbol');
}
private generateAddressMap() {
return this.tknArrToMap('address');
}
private tknArrToMap(key: Exclude<keyof Token, 'error'>) {
const tokens = this.props.allTokens;
return tokens.reduce<{ [k: string]: boolean }>((prev, tk) => {
prev[tk[key]] = true;
return prev;
}, {});
}
}

View File

@ -20,7 +20,7 @@ interface TrackedTokens {
}
interface State {
trackedTokens: { [symbol: string]: boolean };
trackedTokens: TrackedTokens;
showCustomTokenForm: boolean;
}
export default class TokenBalances extends React.PureComponent<Props, State> {
@ -29,10 +29,10 @@ export default class TokenBalances extends React.PureComponent<Props, State> {
showCustomTokenForm: false
};
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.tokenBalances !== this.props.tokenBalances) {
const trackedTokens = nextProps.tokenBalances.reduce<TrackedTokens>((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<Props, State> {
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 = (
<div className="TokenBalances-buttons">
@ -134,6 +134,24 @@ export default class TokenBalances extends React.PureComponent<Props, State> {
});
};
/**
*
* @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 });

View File

@ -30,11 +30,14 @@ export default class TokenRow extends React.PureComponent<Props, State> {
return (
<tr className="TokenRow" onClick={this.handleToggleTracked}>
{this.props.toggleTracked && (
<td className="TokenRow-toggled">
<input type="checkbox" checked={tracked} />
</td>
)}
{/* 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 && (
<td className="TokenRow-toggled">
<input type="checkbox" checked={tracked} />
</td>
)}
<td
className="TokenRow-balance"
title={`${balance.toString()} (Double-Click)`}

View File

@ -1,66 +0,0 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
.BetaAgreement {
@include cover-message;
background: $brand-info;
&-content {
h2 {
text-align: center;
}
&-buttons {
padding-top: 20px;
&-btn {
display: block;
width: 100%;
max-width: 280px;
margin: 0 auto;
border: none;
padding: 0;
transition: $transition;
&.is-continue {
height: 60px;
line-height: 60px;
font-size: 22px;
background: rgba(#fff, 0.96);
color: $gray-dark;
border-radius: 4px;
margin-bottom: 20px;
&:hover {
background: #fff;
color: $gray-darker;
}
}
&.is-reject {
background: none;
color: #fff;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
}
}
}
// Fade out
&.is-fading {
pointer-events: none;
opacity: 0;
background: #fff;
transition: all 500ms ease 400ms;
.BetaAgreement-content {
opacity: 0;
transform: translateY(15px);
transition: all 500ms ease;
}
}
}

View File

@ -1,73 +0,0 @@
import React from 'react';
import { NewTabLink } from 'components/ui';
import { discordURL } from 'config';
import './index.scss';
const LS_KEY = 'acknowledged-beta';
interface State {
isFading: boolean;
hasAcknowledged: boolean;
}
export default class BetaAgreement extends React.PureComponent<{}, State> {
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 (
<div className={`BetaAgreement ${isFading}`}>
<div className="BetaAgreement-content">
<h2>Welcome to the New MyCrypto Beta Release Candidate!</h2>
<p>
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.
</p>
<p>We hope to move this version of MyCrypto into production in the near future!</p>
<p>
Feedback and bug reports are greatly appreciated. You can file issues on our{' '}
<NewTabLink href="https://github.com/MyCryptoHQ/MyCrypto/issues">
GitHub repository
</NewTabLink>{' '}
or join our <NewTabLink href={discordURL}>Discord server</NewTabLink> to discuss the
beta.
</p>
<p>Are you sure you would like to continue?</p>
<div className="BetaAgreement-content-buttons">
<button
className="BetaAgreement-content-buttons-btn is-continue"
onClick={this.doContinue}
>
Yes, continue to the Beta RC
</button>
<button className="BetaAgreement-content-buttons-btn is-reject" onClick={this.reject}>
No, take me to the production site
</button>
</div>
</div>
</div>
);
}
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');
};
}

View File

@ -20,7 +20,12 @@ class DetailsClass extends Component<StateProps> {
<div className="tx-modal-details">
<label className="input-group">
<div className="input-group-header">Network</div>
<Input readOnly={true} value={`${network} network - provided by ${service}`} />
<Input
isValid={true}
showValidAsPlain={true}
readOnly={true}
value={`${network} network - provided by ${service}`}
/>
</label>
<SerializedTransaction

View File

@ -29,7 +29,7 @@ class CurrentCustomMessageClass extends PureComponent<Props, State> {
this.setAddressState(this.props);
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.wallet !== nextProps.wallet) {
this.setAddressState(nextProps);
}

View File

@ -117,7 +117,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-9 input-group flex-grow-1">
<div className="input-group-header">{translate('CUSTOM_NODE_NAME')}</div>
<Input
className={`input-group-input ${this.state.name && invalids.name ? 'invalid' : ''}`}
isValid={!(this.state.name && invalids.name)}
type="text"
placeholder="My Node"
value={this.state.name}
@ -142,9 +142,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_NAME')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkId && invalids.customNetworkId ? 'invalid' : ''
}`}
isValid={!(this.state.customNetworkId && invalids.customNetworkId)}
type="text"
placeholder="My Custom Network"
value={this.state.customNetworkId}
@ -154,9 +152,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_CURRENCY')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkUnit && invalids.customNetworkUnit ? 'invalid' : ''
}`}
isValid={!(this.state.customNetworkUnit && invalids.customNetworkUnit)}
type="text"
placeholder="ETH"
value={this.state.customNetworkUnit}
@ -166,11 +162,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_CHAIN_ID')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkChainId && invalids.customNetworkChainId
? 'invalid'
: ''
}`}
isValid={!(this.state.customNetworkChainId && invalids.customNetworkChainId)}
type="text"
placeholder="1"
value={this.state.customNetworkChainId}
@ -183,7 +175,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_URL')}</div>
<Input
className={`input-group-input ${this.state.url && invalids.url ? 'invalid' : ''}`}
isValid={!(this.state.url && invalids.url)}
type="text"
placeholder="https://127.0.0.1:8545/"
value={this.state.url}
@ -207,9 +199,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('INPUT_USERNAME_LABEL')}</div>
<Input
className={`input-group-input ${
this.state.username && invalids.username ? 'invalid' : ''
}`}
isValid={!(this.state.username && invalids.username)}
type="text"
value={this.state.username}
onChange={e => this.setState({ username: e.currentTarget.value })}
@ -218,9 +208,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('INPUT_PASSWORD_LABEL')}</div>
<Input
className={`input-group-input ${
this.state.password && invalids.password ? 'invalid' : ''
}`}
isValid={!(this.state.password && invalids.password)}
type="password"
value={this.state.password}
onChange={e => this.setState({ password: e.currentTarget.value })}

View File

@ -6,12 +6,12 @@ import { Input } from 'components/ui';
export const DataField: React.SFC<{}> = () => (
<DataFieldFactory
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
withProps={({ data: { raw }, validData, onChange, readOnly }) => (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">{translate('OFFLINE_STEP2_LABEL_6')}</div>
<Input
className={dataExists ? 'is-valid' : 'is-invalid'}
isValid={validData}
type="text"
placeholder={donationAddressMap.ETH}
value={raw}

View File

@ -7,7 +7,7 @@ import { isEtherTransaction } from 'selectors/transaction';
import { AppState } from 'reducers';
export interface CallBackProps {
data: AppState['transaction']['fields']['data'];
dataExists: boolean;
validData: boolean;
readOnly: boolean;
onChange(ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>): void;
}

View File

@ -1,9 +1,10 @@
import React, { Component } from 'react';
import { Query } from 'components/renderCbs';
import { getData, getDataExists } from 'selectors/transaction';
import { getData } from 'selectors/transaction';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { CallBackProps } from 'components/DataFieldFactory';
import { isHexString } from 'ethereumjs-util';
interface OwnProps {
withProps(props: CallBackProps): React.ReactElement<any> | null;
@ -11,19 +12,19 @@ interface OwnProps {
}
interface StateProps {
data: AppState['transaction']['fields']['data'];
dataExists: boolean;
validData: boolean;
}
type Props = OwnProps & StateProps;
class DataInputClass extends Component<Props> {
public render() {
const { data, onChange, dataExists } = this.props;
const { data, onChange, validData } = this.props;
return (
<Query
params={['readOnly']}
withQuery={({ readOnly }) =>
this.props.withProps({ data, onChange, readOnly: !!readOnly, dataExists })
this.props.withProps({ data, onChange, readOnly: !!readOnly, validData })
}
/>
);
@ -32,5 +33,5 @@ class DataInputClass extends Component<Props> {
export const DataInput = connect((state: AppState) => ({
data: getData(state),
dataExists: getDataExists(state)
validData: getData(state).raw === '' || isHexString(getData(state).raw)
}))(DataInputClass);

View File

@ -12,7 +12,6 @@ import React from 'react';
import PreFooter from './PreFooter';
import DisclaimerModal from 'components/DisclaimerModal';
import { NewTabLink } from 'components/ui';
import OnboardModal from 'containers/OnboardModal';
import './index.scss';
import { translateRaw } from 'translations';
@ -82,6 +81,9 @@ export default class Footer extends React.PureComponent<Props, State> {
<NewTabLink href="https://about.mycrypto.com">
{translateRaw('FOOTER_TEAM')}
</NewTabLink>
<NewTabLink href="https://about.mycrypto.com/privacy/">
{translateRaw('FOOTER_PRIVACY_POLICY')}
</NewTabLink>
</div>
<p className="Footer-about-text">{translateRaw('FOOTER_ABOUT')}</p>
@ -130,8 +132,6 @@ export default class Footer extends React.PureComponent<Props, State> {
</div>
</div>
</footer>
<OnboardModal />
<DisclaimerModal isOpen={this.state.isDisclaimerOpen} handleClose={this.toggleModal} />
</div>
);

View File

@ -30,7 +30,7 @@ export const GasLimitField: React.SFC<Props> = ({
/>
</div>
<Input
className={gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}
isValid={gasLimitValidator(raw)}
type="number"
placeholder="21000"
readOnly={!!readOnly}

View File

@ -41,7 +41,7 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
}
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.privateKey !== this.props.privateKey) {
this.setState({ privateKey: nextProps.privateKey || '' });
}

View File

@ -12,7 +12,6 @@
.OnlineStatus {
position: relative;
top: -2px;
width: 12px;
height: 12px;
text-align: center;

View File

@ -7,8 +7,8 @@ interface Props {
}
const OnlineStatus: React.SFC<Props> = ({ isOffline }) => (
<div className={`OnlineStatus fa-stack ${isOffline ? 'is-offline' : 'is-online'}`}>
<Tooltip>{isOffline ? 'Offline' : 'Online'}</Tooltip>
<div className={`OnlineStatus ${isOffline ? 'is-offline' : 'is-online'}`}>
<Tooltip direction="left">{isOffline ? 'Offline' : 'Online'}</Tooltip>
</div>
);

View File

@ -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<IGitHubRelease> {
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}
>
<h5>{translateRaw('APP_UPDATE_BODY')}</h5>
<h4>
{translateRaw('APP_UPDATE_BODY')} {this.versionCompareStr()}
</h4>
</Modal>
);
}
private versionCompareStr() {
return (
<>
<h5>Current Version: {VERSION}</h5>
<h5>New Version: {this.state.newRelease}</h5>
</>
);
}
private close = () => {
this.setState({ isOpen: false });
};

View File

@ -42,7 +42,8 @@ class NonceField extends React.Component<Props> {
/>
</div>
<Input
className={`Nonce-field-input ${!!value ? 'is-valid' : 'is-invalid'}`}
isValid={!!value}
className="Nonce-field-input"
type="number"
placeholder="7"
value={raw}

View File

@ -64,7 +64,7 @@ class OnlineSendClass extends Component<Props, State> {
);
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.transactionBroadcasted && this.state.showModal) {
this.closeModal();
}

View File

@ -39,7 +39,7 @@ export default class SubTabs extends React.PureComponent<Props, State> {
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) {

View File

@ -76,7 +76,7 @@ class TXMetaDataPanel extends React.Component<Props, State> {
}
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (
(this.props.offline && !nextProps.offline) ||
this.props.network.unit !== nextProps.network.unit

View File

@ -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<Props, State> {
<div className="input-group-header">
{translateRaw('OFFLINE_STEP2_LABEL_3')} (gwei)
</div>
{/*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 */}
<Input
className={!!gasPrice.raw && !validGasPrice ? 'invalid' : ''}
type="number"
isValid={validGasPrice}
placeholder="40"
value={gasPrice.raw}
onChange={this.handleGasPriceChange}
@ -173,7 +174,7 @@ class AdvancedGas extends React.Component<Props, State> {
private handleGasPriceChange = (ev: React.FormEvent<HTMLInputElement>) => {
const { value } = ev.currentTarget;
this.props.inputGasPrice(sanitizeNumericalInput(value));
this.props.inputGasPrice(value);
};
private handleToggleAutoGasLimit = (_: React.FormEvent<HTMLInputElement>) => {

View File

@ -54,6 +54,9 @@ class FeeSummary extends React.Component<Props> {
scheduleGasLimit
} = this.props;
if (!gasPrice.value || gasPrice.value.eqn(0) || !gasLimit.value || gasLimit.value.eqn(0)) {
return null;
}
if (isGasEstimating) {
return (
<div className="FeeSummary is-loading">

View File

@ -57,7 +57,7 @@ class SimpleGas extends React.Component<Props> {
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());

View File

@ -40,7 +40,7 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
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<Props, State>
<div className={`TogglablePassword input-group input-group-inline`}>
{isTextareaWhenVisible && isVisible ? (
<TextArea
className={`${className} ${!isValid ? 'invalid' : ''}`}
isValid={!!isValid}
className={className}
value={value}
name={name}
disabled={disabled}
@ -84,11 +85,12 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
/>
) : (
<Input
isValid={!!isValid}
value={value}
name={name}
disabled={disabled}
type={isVisible ? 'text' : 'password'}
className={`${className} ${!isValid ? 'invalid' : ''} border-rad-right-0`}
className={`${className} border-rad-right-0`}
placeholder={placeholder}
onChange={onChange}
onFocus={onFocus}

View File

@ -31,7 +31,7 @@ class TransactionStatus extends React.Component<Props> {
this.props.fetchTransactionData(this.props.txHash);
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.txHash !== nextProps.txHash) {
this.props.fetchTransactionData(nextProps.txHash);
}

View File

@ -226,7 +226,7 @@ const WalletDecrypt = withRouter<Props>(
hasAcknowledgedInsecure: false
};
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
// Reset state when unlock is hidden / revealed
if (nextProps.hidden !== this.props.hidden) {
this.setState({

View File

@ -37,12 +37,6 @@
padding-right: $space;
border: none;
}
.form-control {
display: inline-block;
width: auto;
margin: 0 0 0 10px;
}
}
&-addresses {

View File

@ -73,7 +73,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
this.getAddresses();
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
const { publicKey, chainCode, seed, dPath } = this.props;
if (
nextProps.publicKey !== publicKey ||
@ -132,7 +132,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
<React.Fragment>
<div className="DWModal-path-custom">
<Input
className={customPath ? (isValidPath(customPath) ? 'valid' : 'invalid') : ''}
isValid={customPath ? isValidPath(customPath) : true}
value={customPath}
placeholder="m/44'/60'/0'/0"
onChange={this.handleChangeCustomPath}

View File

@ -65,9 +65,8 @@ export class KeystoreDecrypt extends PureComponent {
{isWalletPending ? <Spinner /> : ''}
<Input
className={`${password.length > 0 ? 'is-valid' : 'is-invalid'} ${
file.length && isWalletPending ? 'hidden' : ''
}`}
isValid={password.length > 0}
className={`${file.length && isWalletPending ? 'hidden' : ''}`}
disabled={!file}
value={password}
onChange={this.onPasswordChange}

View File

@ -50,7 +50,7 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
});
};
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
this.setState({ dPath: nextProps.dPath });
}

View File

@ -39,7 +39,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
dPath: this.props.dPath
};
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath) {
this.setState({ dPath: nextProps.dPath });
}
@ -67,6 +67,8 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
<div className="form-group">
<p>{translate('ADD_LABEL_8')}</p>
<Input
isValid={true}
showValidAsPlain={true}
value={pass}
onChange={this.onPasswordChange}
placeholder={translateRaw('INPUT_PASSWORD_LABEL')}

View File

@ -74,7 +74,7 @@ export class PrivateKeyDecrypt extends PureComponent<Props> {
<label className="input-group">
<div className="input-group-header">{translate('ADD_LABEL_3')}</div>
<Input
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
isValid={password.length > 0}
value={password}
onChange={this.onPasswordChange}
onKeyDown={this.onKeyDown}

View File

@ -41,7 +41,7 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
isLoading: false
};
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
this.setState({ dPath: nextProps.dPath });
}

View File

@ -60,7 +60,8 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
)}
<Input
className={`ViewOnly-input ${isValid ? 'is-valid' : 'is-invalid'}`}
isValid={isValid}
className="ViewOnly-input"
value={address}
onChange={this.changeAddress}
placeholder={translateRaw('VIEW_ONLY_ENTER')}

View File

@ -0,0 +1,26 @@
@import 'common/sass/variables';
.WelcomeModal {
font-size: $font-size-bump-more;
&-logo {
display: block;
max-width: 380px;
margin: $space auto $space * 2;
}
&-beta {
margin-top: -$space-md;
font-size: $font-size-base;
text-align: center;
}
&-continue {
display: block;
margin: $space * 2 auto 0;
}
p, ul {
margin-bottom: $space;
}
}

View File

@ -0,0 +1,62 @@
import React from 'react';
import translate from 'translations';
import { Modal, NewTabLink } from 'components/ui';
import { isLegacyUser, isBetaUser } from 'utils/localStorage';
import Logo from 'assets/images/logo-mycrypto-transparent.svg';
import './WelcomeModal.scss';
const LS_KEY = 'acknowledged-welcome';
interface State {
isOpen: boolean;
}
export default class WelcomeModal extends React.Component<{}, State> {
public state: State = {
isOpen: false
};
public componentDidMount() {
if (isLegacyUser() && !localStorage.getItem(LS_KEY)) {
this.setState({ isOpen: true });
}
}
public render() {
return (
<Modal isOpen={this.state.isOpen} handleClose={this.close} maxWidth={660}>
<div className="WelcomeModal">
<img className="WelcomeModal-logo" src={Logo} />
{isBetaUser() && (
<p className="WelcomeModal-beta alert alert-success">
💖 {translate('WELCOME_MODAL_BETA')} 🚀
</p>
)}
<p>{translate('WELCOME_MODAL_INTRO')}</p>
<ul>
<li>{translate('WELCOME_MODAL_FEATURE_1')}</li>
<li>{translate('WELCOME_MODAL_FEATURE_2')}</li>
<li>{translate('WELCOME_MODAL_FEATURE_3')}</li>
<li>{translate('WELCOME_MODAL_FEATURE_4')}</li>
<li>
<NewTabLink href="https://download.mycrypto.com/">
{translate('WELCOME_MODAL_FEATURE_5')}
</NewTabLink>
</li>
<li>{translate('WELCOME_MODAL_FEATURE_MORE')}</li>
</ul>
<p>{translate('WELCOME_MODAL_LINKS')}</p>
<button className="WelcomeModal-continue btn btn-lg btn-primary" onClick={this.close}>
{translate('WELCOME_MODAL_CONTINUE')}
</button>
</div>
</Modal>
);
}
private close = () => {
this.setState({ isOpen: false });
localStorage.setItem(LS_KEY, 'true');
};
}

View File

@ -1,25 +0,0 @@
import React, { Component } from 'react';
interface RequiredProps {
condition: boolean;
conditionalProps: {
[key: string]: any;
};
}
/**
* Optional
*/
export const withConditional = <WrappedComponentProps extends {}>(
PassedComponent: React.ComponentType<WrappedComponentProps>
) =>
class extends Component<WrappedComponentProps & RequiredProps, {}> {
public render() {
const { condition, conditionalProps, ...passedProps } = this.props as any;
return condition ? (
<PassedComponent {...{ ...passedProps, ...(conditionalProps as object) }} />
) : (
<PassedComponent {...passedProps} />
);
}
};

View File

@ -1 +0,0 @@
export * from './Conditional';

View File

@ -14,7 +14,6 @@ export { default as Header } from './Header';
export { default as Footer } from './Footer';
export { default as BalanceSidebar } from './BalanceSidebar';
export { default as PaperWallet } from './PaperWallet';
export { default as BetaAgreement } from './BetaAgreement';
export { default as TXMetaDataPanel } from './TXMetaDataPanel';
export { default as WalletDecrypt } from './WalletDecrypt';
export { default as TogglablePassword } from './TogglablePassword';

View File

@ -29,7 +29,7 @@ const initialState = { userInput: '' };
class UnitConverterClass extends Component<Props, State> {
public state: State = initialState;
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
const { userInput } = this.state;
if (this.props.decimal !== nextProps.decimal) {

View File

@ -1,6 +0,0 @@
import React from 'react';
import { withConditional } from 'components/hocs';
import { Input } from 'components/ui';
const inpt: React.SFC<React.InputHTMLAttributes<any>> = props => <Input {...props} />;
export const ConditionalInput = withConditional(inpt);

View File

@ -85,13 +85,13 @@
border-color: $brand-danger;
box-shadow: inset 0px 0px 0px 1px $brand-danger;
}
&.valid.has-value {
border-color: #8dd17b;
box-shadow: inset 0px 0px 0px 1px #8dd17b;
}
&:focus {
border-color: #4295bc;
box-shadow: inset 0px 0px 0px 1px #4295bc;
&.valid {
border-color: #8dd17b;
box-shadow: inset 0px 0px 0px 1px #8dd17b;
}
}
}
}

View File

@ -1,33 +1,62 @@
import React, { HTMLProps } from 'react';
import classnames from 'classnames';
import './Input.scss';
interface State {
hasBlurred: boolean;
/**
* @description when the input has not had any values inputted yet
* e.g. "Pristine" condition
*/
isStateless: boolean;
}
class Input extends React.Component<HTMLProps<HTMLInputElement>, State> {
interface OwnProps extends HTMLProps<HTMLInputElement> {
isValid: boolean;
showValidAsPlain?: boolean;
}
class Input extends React.Component<OwnProps, State> {
public state: State = {
hasBlurred: false
hasBlurred: false,
isStateless: true
};
public render() {
const { showValidAsPlain, isValid, ...htmlProps } = this.props;
const hasValue = !!this.props.value && this.props.value.toString().length > 0;
const classname = classnames(
this.props.className,
'input-group-input',
this.state.isStateless ? '' : isValid ? (showValidAsPlain ? '' : '') : `invalid`,
this.state.hasBlurred && 'has-blurred',
hasValue && 'has-value'
);
return (
<input
{...this.props}
{...htmlProps}
onBlur={e => {
this.setState({ hasBlurred: true });
if (this.props && this.props.onBlur) {
this.props.onBlur(e);
}
}}
onChange={this.handleOnChange}
onWheel={this.props.type === 'number' ? this.preventNumberScroll : undefined}
className={`input-group-input ${this.props.className} ${
this.state.hasBlurred ? 'has-blurred' : ''
} ${!!this.props.value && this.props.value.toString().length > 0 ? 'has-value' : ''}`}
className={classname}
/>
);
}
private handleOnChange = (args: React.FormEvent<HTMLInputElement>) => {
if (this.state.isStateless) {
this.setState({ isStateless: false });
}
if (this.props.onChange) {
this.props.onChange(args);
}
};
// When number inputs are scrolled on while in focus, the number changes. So we blur
// it if it's focused to prevent that behavior, without preventing the scroll.
private preventNumberScroll(ev: React.WheelEvent<HTMLInputElement>) {

View File

@ -3,8 +3,8 @@ import closeIcon from 'assets/images/close.svg';
import { IButton } from 'components/ui/Modal';
interface Props {
title?: string;
children: any;
title?: React.ReactNode;
children: React.ReactNode;
modalStyle?: CSSProperties;
hasButtons?: number;
buttons?: IButton[];
@ -67,7 +67,7 @@ export default class ModalBody extends React.Component<Props> {
<div className="Modal-content" ref={div => (this.modalContent = div as HTMLElement)}>
{children}
<div className="Modal-fade" />
<div className={`Modal-fade ${!hasButtons ? 'has-no-footer' : ''}`} />
</div>
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
</div>

View File

@ -53,6 +53,10 @@ $m-anim-speed: 400ms;
bottom: 4.5rem;
left: 50%;
transform: translateX(-50%);
&.has-no-footer {
bottom: 0;
}
}
&-header {

View File

@ -12,9 +12,9 @@ export interface IButton {
}
interface Props {
isOpen?: boolean;
title?: string;
title?: React.ReactNode;
disableButtons?: boolean;
children: any;
children: React.ReactNode;
buttons?: IButton[];
maxWidth?: number;
handleClose(): void;

View File

@ -70,14 +70,7 @@ export default class DropdownComponent<T> extends PureComponent<Props<T>, State>
return (
<ul className={menuClass} style={searchable ? searchableStyle : undefined}>
{searchable && (
<input
className="form-control"
placeholder={'Search'}
onChange={onSearchChange}
value={search}
/>
)}
{searchable && <input placeholder={'Search'} onChange={onSearchChange} value={search} />}
{options
.filter(option => {

View File

@ -15,12 +15,12 @@ interface State {
export default class QRCode extends React.PureComponent<Props, State> {
public state: State = {};
public componentWillMount() {
public UNSAFE_componentWillMount() {
// Start generating QR codes immediately
this.generateQrCode(this.props.data);
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
// Regenerate QR codes if props change
if (nextProps.data !== this.props.data) {
this.generateQrCode(nextProps.data);

View File

@ -1,27 +0,0 @@
import React, { PureComponent } from 'react';
interface Props {
value?: string;
options: string[];
onChange(event: React.FormEvent<HTMLSpanElement>): void;
}
export default class SimpleSelect extends PureComponent<Props, {}> {
public render() {
return (
<select
value={this.props.value || this.props.options[0]}
className={'form-control'}
onChange={this.props.onChange}
>
{this.props.options.map((obj, i) => {
return (
<option value={obj} key={i}>
{obj}
</option>
);
})}
</select>
);
}
}

View File

@ -34,7 +34,7 @@ class SwapDropdown extends PureComponent<Props, State> {
public dropdown: HTMLDivElement | null;
public componentWillMount() {
public UNSAFE_componentWillMount() {
this.buildOptions(this.props.options);
document.addEventListener('click', this.handleBodyClick);
}
@ -43,7 +43,7 @@ class SwapDropdown extends PureComponent<Props, State> {
document.removeEventListener('click', this.handleBodyClick);
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.options !== nextProps.options) {
this.buildOptions(nextProps.options);
}

View File

@ -1,30 +1,59 @@
import React, { HTMLProps } from 'react';
import classnames from 'classnames';
import './Input.scss';
interface State {
hasBlurred: boolean;
/**
* @description when the input has not had any values inputted yet
* e.g. "Pristine" condition
*/
isStateless: boolean;
}
class TextArea extends React.Component<HTMLProps<HTMLTextAreaElement>, State> {
interface OwnProps extends HTMLProps<HTMLTextAreaElement> {
isValid: boolean;
showValidAsPlain?: boolean;
}
class TextArea extends React.Component<OwnProps, State> {
public state: State = {
hasBlurred: false
hasBlurred: false,
isStateless: true
};
public render() {
const { showValidAsPlain, isValid, ...htmlProps } = this.props;
const classname = classnames(
this.props.className,
'input-group-input',
this.state.isStateless ? '' : isValid ? (showValidAsPlain ? '' : '') : `invalid`,
this.state.hasBlurred && 'has-blurred'
);
return (
<textarea
{...this.props}
{...htmlProps}
onBlur={e => {
this.setState({ hasBlurred: true });
if (this.props && this.props.onBlur) {
this.props.onBlur(e);
}
}}
className={`input-group-input ${this.props.className} ${
this.state.hasBlurred ? 'has-blurred' : ''
}`}
onChange={this.handleOnChange}
className={classname}
/>
);
}
private handleOnChange = (args: React.FormEvent<HTMLTextAreaElement>) => {
if (this.state.isStateless) {
this.setState({ isStateless: false });
}
if (this.props.onChange) {
this.props.onChange(args);
}
};
}
export default TextArea;

View File

@ -1,27 +0,0 @@
@import 'common/sass/variables';
$height: 22px;
// TODO - Implement styles for custom title bar on all platforms
.TitleBar,
.TitleBarPlaceholder {
display: none;
}
.TitleBar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: $height;
line-height: $height;
-webkit-user-select: none;
-webkit-app-region: drag;
background: $body-bg;
z-index: $zindex-top;
box-shadow: 0 1px 1px rgba(#000, 0.08);
}
.TitleBarPlaceholder {
height: $height;
}

View File

@ -1,11 +0,0 @@
import React from 'react';
import './TitleBar.scss';
const TitleBar: React.SFC<{}> = () => (
<React.Fragment>
<div className="TitleBar" />
<div className="TitleBarPlaceholder" />
</React.Fragment>
);
export default TitleBar;

View File

@ -1,9 +1,9 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
$tooltip-bg: rgba(#222, 0.95);
.Tooltip {
display: flex;
justify-content: center;
position: absolute;
top: 0;
left: 50%;
@ -14,7 +14,7 @@ $tooltip-bg: rgba(#222, 0.95);
pointer-events: none;
opacity: 0;
visibility: hidden;
transform: translate(-50%, -100%) translateY(-5px);
transform: translate(-50%, -100%) translateY(-$tooltip-start-distance);
transition-property: opacity, transform, visibility;
transition-duration: 100ms, 100ms, 0ms;
transition-delay: 0ms, 0ms, 100ms;
@ -32,7 +32,7 @@ $tooltip-bg: rgba(#222, 0.95);
bottom: 0;
left: 50%;
transform: translate(-50%, 100%);
@include triangle(10px, $tooltip-bg, down);
@include triangle($tooltip-arrow-size * 2, $tooltip-bg, down);
}
}
@ -46,7 +46,7 @@ $tooltip-bg: rgba(#222, 0.95);
border-radius: 2px;
&:after {
@include triangle(8px, $tooltip-bg, down);
border-width: $tooltip-arrow-size - 1;
}
}
}
@ -60,8 +60,55 @@ $tooltip-bg: rgba(#222, 0.95);
border-radius: 4px;
&:after {
@include triangle(12px, $tooltip-bg, down);
border-width: $tooltip-arrow-size + 1;
}
}
}
// Direction, top is default
&.is-direction-left {
left: 0;
top: 50%;
justify-content: flex-end;
transform: translate(-100%, -50%) translateX(-$tooltip-start-distance);
> span:after {
bottom: 50%;
right: 0;
left: auto;
transform: translate(100%, 50%);
border-top-color: transparent;
border-left-color: $tooltip-bg;
}
}
&.is-direction-right {
left: auto;
right: 0;
top: 50%;
justify-content: flex-start;
transform: translate(100%, -50%) translateX($tooltip-start-distance);
> span:after {
bottom: 50%;
left: 0;
transform: translate(-100%, 50%);
border-top-color: transparent;
border-right-color: $tooltip-bg;
}
}
&.is-direction-bottom {
top: auto;
bottom: 0;
transform: translate(-50%, 100%) translateY($tooltip-start-distance);
> span:after {
bottom: auto;
top: 0;
transform: translate(-50%, -100%);
border-top-color: transparent;
border-bottom-color: $tooltip-bg;
}
}
}

View File

@ -5,13 +5,15 @@ import './Tooltip.scss';
interface Props {
children: React.ReactElement<string> | string;
size?: 'sm' | 'md' | 'lg';
direction?: 'top' | 'bottom' | 'left' | 'right';
}
const Tooltip: React.SFC<Props> = ({ size, children }) => (
const Tooltip: React.SFC<Props> = ({ size, direction, children }) => (
<div
className={classnames({
Tooltip: true,
[`is-size-${size}`]: !!size
[`is-size-${size}`]: !!size,
[`is-direction-${direction}`]: !!direction
})}
>
<span className="Tooltip-text">{children}</span>

View File

@ -11,13 +11,11 @@ export { default as UnitDisplay } from './UnitDisplay';
export { default as Spinner } from './Spinner';
export { default as SwapDropdown } from './SwapDropdown';
export { default as Tooltip } from './Tooltip';
export { default as TitleBar } from './TitleBar';
export { default as HelpLink } from './HelpLink';
export { default as Input } from './Input';
export { default as TextArea } from './TextArea';
export { default as Address } from './Address';
export { default as CodeBlock } from './CodeBlock';
export { default as Toggle } from './Toggle';
export * from './ConditionalInput';
export * from './Expandable';
export * from './InlineSpinner';

View File

@ -1,16 +1,17 @@
import React from 'react'; // For ANNOUNCEMENT_MESSAGE jsx
import NewTabLink from 'components/ui/NewTabLink';
import { getValues } from '../utils/helpers';
import packageJson from '../../package.json';
import { GasPriceSetting } from 'types/network';
import { makeExplorer } from 'utils/helpers';
import NewTabLink from 'components/ui/NewTabLink';
export const languages = require('./languages.json');
export const discordURL = 'https://discord.gg/VSaTXEA';
// Displays in the footer
export const VERSION_RAW = packageJson.version;
export const VERSION = `${VERSION_RAW} (Release Candidate 2)`;
const VERSION_ELECTRON = packageJson['electron-version'];
const VERSION_WEB = packageJson.version;
export const VERSION = process.env.BUILD_ELECTRON ? VERSION_ELECTRON : VERSION_WEB;
export const N_FACTOR = 8192;
// Bricks the app once this date has been exceeded. Remember to update these 2
@ -18,7 +19,6 @@ export const N_FACTOR = 8192;
// It is currently set to: 05/25/2018 @ 12:00am (UTC)
// TODO: Remove me once app alpha / release candidates are done
export const APP_ALPHA_EXPIRATION = 1527206400000;
export const VERSION_RC = `${packageJson.version}-RC.0`;
// Displays at the top of the site, make message empty string to remove.
// Type can be primary, warning, danger, success, info, or blank for grey.
@ -26,10 +26,8 @@ export const VERSION_RC = `${packageJson.version}-RC.0`;
export const ANNOUNCEMENT_TYPE = '';
export const ANNOUNCEMENT_MESSAGE = (
<React.Fragment>
This is a Beta Release Candidate of the new MyCrypto. Please submit any bug reports to our{' '}
<NewTabLink href="https://github.com/MyCryptoHQ/MyCrypto/issues">GitHub</NewTabLink> and use{' '}
<NewTabLink href="https://hackerone.com/mycrypto">HackerOne</NewTabLink> for critical
vulnerabilities. Join the discussion on <NewTabLink href={discordURL}>Discord</NewTabLink>.
Welcome to the new MyCrypto. We hope you like it! If it's urgent and you need the old site, you
can still use <NewTabLink href="https://legacy.mycrypto.com">MyCrypto Legacy</NewTabLink>
</React.Fragment>
);

View File

@ -47,6 +47,10 @@ export const socialMediaLinks: Link[] = [
];
export const productLinks: Link[] = [
{
link: 'https://legacy.mycrypto.com/',
text: translateRaw('OLD_MYCRYPTO')
},
{
link:
'https://chrome.google.com/webstore/detail/etheraddresslookup/pdknmigbbbhmllnmgdfalmedcmcefdfn',
@ -62,8 +66,12 @@ export const productLinks: Link[] = [
text: translateRaw('ETHERSCAMDB')
},
{
link: 'https://www.mycrypto.com/helpers.html',
link: 'https://legacy.mycrypto.com/helpers.html',
text: translateRaw('FOOTER_HELP_AND_DEBUGGING')
},
{
link: 'https://hackerone.com/mycrypto',
text: translateRaw('FOOTER_HACKERONE')
}
];

View File

@ -24,6 +24,16 @@
"symbol": "REN",
"decimal": 18
},
{
"address": "0x37427576324fE1f3625c9102674772d7CF71377d",
"symbol": "SGT (SelfieYo Gold Token)",
"decimal": 18
},
{
"address": "0xd248B0D48E44aaF9c49aea0312be7E13a6dc1468",
"symbol": "SGT (SGT)",
"decimal": 1
},
{
"address": "0x78B7FADA55A64dD895D8c8c35779DD8b67fA8a05",
"symbol": "ATL",
@ -49,6 +59,11 @@
"symbol": "SNGLS",
"decimal": 0
},
{
"address": "0x515669d308f887Fd83a471C7764F5d084886D34D",
"symbol": "MUXE",
"decimal": 18
},
{
"address": "0x025abAD9e518516fdaAFBDcdB9701b37fb7eF0FA",
"symbol": "GTKT",
@ -64,6 +79,11 @@
"symbol": "STRC",
"decimal": 8
},
{
"address": "0x2a8E98e256f32259b5E5Cb55Dd63C8e891950666",
"symbol": "PTC",
"decimal": 18
},
{
"address": "0x41e5560054824eA6B0732E656E3Ad64E20e94E45",
"symbol": "CVC",
@ -139,6 +159,11 @@
"symbol": "FAM",
"decimal": 12
},
{
"address": "0x105d97ef2E723f1cfb24519Bc6fF15a6D091a3F1",
"symbol": "UMKA",
"decimal": 4
},
{
"address": "0x694404595e3075A942397F466AAcD462FF1a7BD0",
"symbol": "PATENTS",
@ -184,6 +209,11 @@
"symbol": "GANA",
"decimal": 18
},
{
"address": "0xC5bBaE50781Be1669306b9e001EFF57a2957b09d",
"symbol": "GTO",
"decimal": 5
},
{
"address": "0x9e88613418cF03dCa54D6a2cf6Ad934A78C7A17A",
"symbol": "SWM",
@ -199,6 +229,31 @@
"symbol": "OMG",
"decimal": 18
},
{
"address": "0xf9F7c29CFdf19FCf1f2AA6B84aA367Bcf1bD1676",
"symbol": "DTT",
"decimal": 18
},
{
"address": "0x78Eb8DC641077F049f910659b6d580E80dC4d237",
"symbol": "SMT (Social Media Market)",
"decimal": 8
},
{
"address": "0x55F93985431Fc9304077687a35A1BA103dC1e081",
"symbol": "SMT (SmartMesh)",
"decimal": 18
},
{
"address": "0x2dCFAAc11c9EebD8C6C42103Fe9e2a6AD237aF27",
"symbol": "SMT (Smart Node)",
"decimal": 18
},
{
"address": "0x82125AFe01819Dff1535D0D6276d57045291B6c0",
"symbol": "MRL",
"decimal": 18
},
{
"address": "0x7DD7F56D697Cc0f2b52bD55C057f378F1fE6Ab4b",
"symbol": "$TEAK",
@ -219,11 +274,26 @@
"symbol": "ARN",
"decimal": 8
},
{
"address": "0x464eBE77c293E473B48cFe96dDCf88fcF7bFDAC0",
"symbol": "KRL",
"decimal": 18
},
{
"address": "0x1014613E2B3CBc4d575054D4982E580d9b99d7B1",
"symbol": "BCV",
"decimal": 8
},
{
"address": "0xd94F2778e2B3913C53637Ae60647598bE588c570",
"symbol": "PRPS (1)",
"decimal": 18
},
{
"address": "0x7641b2Ca9DDD58adDf6e3381c1F994Aac5f1A32f",
"symbol": "PRPS (2)",
"decimal": 18
},
{
"address": "0xdd94De9cFE063577051A5eb7465D08317d8808B6",
"symbol": "Devcon2 Token",
@ -284,6 +354,16 @@
"symbol": "DIVX",
"decimal": 18
},
{
"address": "0xe25bCec5D3801cE3a794079BF94adF1B8cCD802D",
"symbol": "MAN",
"decimal": 18
},
{
"address": "0x5adc961D6AC3f7062D2eA45FEFB8D8167d44b190",
"symbol": "DTH",
"decimal": 18
},
{
"address": "0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0",
"symbol": "EOS",
@ -405,13 +485,8 @@
"decimal": 18
},
{
"address": "0x55F93985431Fc9304077687a35A1BA103dC1e081",
"symbol": "SMT (SmartMesh)",
"decimal": 18
},
{
"address": "0x2dCFAAc11c9EebD8C6C42103Fe9e2a6AD237aF27",
"symbol": "SMT (Smart Node)",
"address": "0x38c87AA89B2B8cD9B95b736e1Fa7b612EA972169",
"symbol": "AMO",
"decimal": 18
},
{
@ -479,6 +554,11 @@
"symbol": "XID",
"decimal": 8
},
{
"address": "0xCd4b4b0F3284a33AC49C67961EC6e111708318Cf",
"symbol": "AX1",
"decimal": 5
},
{
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
@ -524,6 +604,21 @@
"symbol": "BTT",
"decimal": 0
},
{
"address": "0x9fC0583220eB44fAeE9e2dc1E63F39204DDD9090",
"symbol": "2DC",
"decimal": 18
},
{
"address": "0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6",
"symbol": "ABYSS",
"decimal": 18
},
{
"address": "0xC87c5dD86A3d567fF28701886fB0745aaa898da4",
"symbol": "CTG",
"decimal": 18
},
{
"address": "0xfbd0d1c77B501796A35D86cF91d65D9778EeE695",
"symbol": "TWNKL",
@ -626,7 +721,12 @@
},
{
"address": "0xD4CffeeF10F60eCA581b5E1146B5Aca4194a4C3b",
"symbol": "DUBI",
"symbol": "DUBI (1)",
"decimal": 18
},
{
"address": "0x9c6Fa42209169bCeA032e401188a6fc3e9C9f59c",
"symbol": "DUBI (2)",
"decimal": 18
},
{
@ -709,6 +809,11 @@
"symbol": "DCN",
"decimal": 0
},
{
"address": "0x6e2050CBFB3eD8A4d39b64cC9f47E711a03a5a89",
"symbol": "SSH",
"decimal": 18
},
{
"address": "0x59416A25628A76b4730eC51486114c32E0B582A1",
"symbol": "PLASMA",
@ -729,6 +834,11 @@
"symbol": "CCC (ICONOMI)",
"decimal": 18
},
{
"address": "0x075c60EE2cD308ff47873b38Bd9A0Fa5853382c4",
"symbol": "DEEZ",
"decimal": 18
},
{
"address": "0x638AC149eA8EF9a1286C41B977017AA7359E6Cfa",
"symbol": "ALTS",
@ -739,6 +849,11 @@
"symbol": "WCN",
"decimal": 18
},
{
"address": "0xFA1a856Cfa3409CFa145Fa4e20Eb270dF3EB21ab",
"symbol": "IOST",
"decimal": 18
},
{
"address": "0x5BC7e5f0Ab8b2E10D2D0a3F21739FCe62459aeF3",
"symbol": "ENTRP",
@ -824,6 +939,11 @@
"symbol": "NBAI",
"decimal": 18
},
{
"address": "0x6710c63432A2De02954fc0f851db07146a6c0312",
"symbol": "MFG",
"decimal": 18
},
{
"address": "0x65A15014964F2102Ff58647e16a16a6B9E14bCF6",
"symbol": "Ox Fina",
@ -914,6 +1034,11 @@
"symbol": "LA",
"decimal": 18
},
{
"address": "0x7e9e431a0B8c4D532C745B1043c7FA29a48D4fBa",
"symbol": "eosDAC",
"decimal": 18
},
{
"address": "0xb5C33F965C8899D255c34CDD2A3efA8AbCbB3DeA",
"symbol": "KPR",
@ -1069,6 +1194,11 @@
"symbol": "SIG",
"decimal": 18
},
{
"address": "0xDF347911910b6c9A4286bA8E2EE5ea4a39eB2134",
"symbol": "BOB",
"decimal": 18
},
{
"address": "0x4F4f0Db4de903B88f2B1a2847971E231D54F8fd3",
"symbol": "GEE",
@ -1089,6 +1219,11 @@
"symbol": "BTL (Bitlle)",
"decimal": 4
},
{
"address": "0xdD41fBd1Ae95C5D9B198174A28e04Be6b3d1aa27",
"symbol": "LYS",
"decimal": 8
},
{
"address": "0xf3Db5Fa2C66B7aF3Eb0C0b782510816cbe4813b8",
"symbol": "EVX",
@ -1134,6 +1269,11 @@
"symbol": "LGO",
"decimal": 8
},
{
"address": "0xAd8DD4c725dE1D31b9E8F8D146089e9DC6882093",
"symbol": "MIT (Mychatcoin)",
"decimal": 6
},
{
"address": "0xA89b5934863447f6E4Fc53B315a93e873bdA69a3",
"symbol": "LUM",
@ -1174,6 +1314,11 @@
"symbol": "FLX",
"decimal": 18
},
{
"address": "0x554FFc77F4251a9fB3c0E3590a6a205f8d4e067D",
"symbol": "ZMN",
"decimal": 18
},
{
"address": "0x888666CA69E0f178DED6D75b5726Cee99A87D698",
"symbol": "ICN",
@ -1239,6 +1384,11 @@
"symbol": "EDC",
"decimal": 6
},
{
"address": "0xDDe12a12A6f67156e0DA672be05c374e1B0a3e57",
"symbol": "JOY",
"decimal": 6
},
{
"address": "0xEF68e7C694F40c8202821eDF525dE3782458639f",
"symbol": "LRC",
@ -1259,6 +1409,11 @@
"symbol": "RVL",
"decimal": 18
},
{
"address": "0x7f6715c3FC4740A02F70De85B9FD50ac6001fEd9",
"symbol": "FANX",
"decimal": 18
},
{
"address": "0xa74476443119A942dE498590Fe1f2454d7D4aC0d",
"symbol": "GNT",
@ -1339,6 +1494,11 @@
"symbol": "CC3",
"decimal": 18
},
{
"address": "0x0C91B015AbA6f7B4738dcD36E7410138b29ADC29",
"symbol": "COIL",
"decimal": 8
},
{
"address": "0x85e076361cc813A908Ff672F9BAd1541474402b2",
"symbol": "TEL",
@ -1369,6 +1529,16 @@
"symbol": "AIX",
"decimal": 18
},
{
"address": "0x6c6EE5e31d828De241282B9606C8e98Ea48526E2",
"symbol": "HOT (HoloToken)",
"decimal": 18
},
{
"address": "0x9AF839687F6C94542ac5ece2e317dAAE355493A1",
"symbol": "HOT (Hydro Protocol)",
"decimal": 18
},
{
"address": "0x6927C69fb4daf2043fbB1Cb7b86c5661416bea29",
"symbol": "ETR",
@ -1414,6 +1584,11 @@
"symbol": "CTX",
"decimal": 18
},
{
"address": "0xf0Ee6b27b759C9893Ce4f094b49ad28fd15A23e4",
"symbol": "ENG",
"decimal": 8
},
{
"address": "0x1844b21593262668B7248d0f57a220CaaBA46ab9",
"symbol": "PRL",
@ -1444,6 +1619,11 @@
"symbol": "S-A-PAT",
"decimal": 18
},
{
"address": "0xf6b6AA0Ef0f5Edc2C1c5d925477F97eAF66303e7",
"symbol": "XGG",
"decimal": 8
},
{
"address": "0x3618516F45CD3c913F81F9987AF41077932Bc40d",
"symbol": "PCL",
@ -1474,6 +1654,11 @@
"symbol": "onG",
"decimal": 18
},
{
"address": "0x0a9A9ce600D08BF9b76F49FA4e7b38A67EBEB1E6",
"symbol": "GROW",
"decimal": 8
},
{
"address": "0xF26ef5E0545384b7Dcc0f297F2674189586830DF",
"symbol": "BSDC",
@ -1549,6 +1734,11 @@
"symbol": "REBL",
"decimal": 18
},
{
"address": "0xC64500DD7B0f1794807e67802F8Abbf5F8Ffb054",
"symbol": "LOCUS",
"decimal": 18
},
{
"address": "0x83eEA00D838f92dEC4D1475697B9f4D3537b56E3",
"symbol": "VOISE",
@ -1594,6 +1784,11 @@
"symbol": "DCA",
"decimal": 18
},
{
"address": "0x8a77e40936BbC27e80E9a3F526368C967869c86D",
"symbol": "MVP",
"decimal": 18
},
{
"address": "0x9E46A38F5DaaBe8683E10793b06749EEF7D733d1",
"symbol": "NCT",
@ -1645,9 +1840,14 @@
"decimal": 18
},
{
"address": "0xd248B0D48E44aaF9c49aea0312be7E13a6dc1468",
"symbol": "SGT",
"decimal": 1
"address": "0xFcD862985628b254061F7A918035B80340D045d3",
"symbol": "GIF",
"decimal": 18
},
{
"address": "0x922aC473A3cC241fD3a0049Ed14536452D58D73c",
"symbol": "VLD",
"decimal": 18
},
{
"address": "0xfeDAE5642668f8636A11987Ff386bfd215F942EE",
@ -1689,6 +1889,11 @@
"symbol": "DAXT",
"decimal": 18
},
{
"address": "0x523630976eB6147621B5c31c781eBe2Ec2a806E0",
"symbol": "eUSD",
"decimal": 18
},
{
"address": "0xF433089366899D83a9f26A773D59ec7eCF30355e",
"symbol": "MTL",
@ -1739,6 +1944,11 @@
"symbol": "HKY",
"decimal": 18
},
{
"address": "0x2467AA6B5A2351416fD4C3DeF8462d841feeecEC",
"symbol": "QBX",
"decimal": 18
},
{
"address": "0x5dbe296F97B23C4A6AA6183D73e574D02bA5c719",
"symbol": "LUC",
@ -1819,6 +2029,11 @@
"symbol": "CK",
"decimal": 0
},
{
"address": "0x48e5413b73add2434e47504E2a22d14940dBFe78",
"symbol": "INRM",
"decimal": 3
},
{
"address": "0x1e49fF77c355A3e38D6651ce8404AF0E48c5395f",
"symbol": "MTRc",
@ -1844,16 +2059,21 @@
"symbol": "DICE",
"decimal": 16
},
{
"address": "0x9AF839687F6C94542ac5ece2e317dAAE355493A1",
"symbol": "HOT",
"decimal": 18
},
{
"address": "0x744d70FDBE2Ba4CF95131626614a1763DF805B9E",
"symbol": "SNT",
"decimal": 18
},
{
"address": "0x69c4BB240cF05D51eeab6985Bab35527d04a8C64",
"symbol": "OPEN (1)",
"decimal": 8
},
{
"address": "0xe9dE1C630753A15d7021Cc563429c21d4887506F",
"symbol": "OPEN (2)",
"decimal": 8
},
{
"address": "0x1a7a8BD9106F2B8D977E08582DC7d24c723ab0DB",
"symbol": "APPC",
@ -1929,6 +2149,11 @@
"symbol": "SAN",
"decimal": 18
},
{
"address": "0x82fdedfB7635441aA5A92791D001fA7388da8025",
"symbol": "DTx",
"decimal": 18
},
{
"address": "0x8eFFd494eB698cc399AF6231fCcd39E08fd20B15",
"symbol": "PIX",
@ -2024,6 +2249,11 @@
"symbol": "GXVC",
"decimal": 10
},
{
"address": "0x8a854288a5976036A725879164Ca3e91d30c6A1B",
"symbol": "GET",
"decimal": 18
},
{
"address": "0x4c382F8E09615AC86E08CE58266CC227e7d4D913",
"symbol": "SKR",
@ -2109,6 +2339,11 @@
"symbol": "STAC",
"decimal": 18
},
{
"address": "0xfe7B915A0bAA0E79f85c5553266513F7C1c03Ed0",
"symbol": "THUG",
"decimal": 18
},
{
"address": "0xC0Eb85285d83217CD7c891702bcbC0FC401E2D9D",
"symbol": "HVN",
@ -2209,11 +2444,6 @@
"symbol": "GELD",
"decimal": 18
},
{
"address": "0x7641b2Ca9DDD58adDf6e3381c1F994Aac5f1A32f",
"symbol": "PRPS",
"decimal": 18
},
{
"address": "0x0AfFa06e7Fbe5bC9a764C979aA66E8256A631f02",
"symbol": "PLBT",
@ -2269,6 +2499,11 @@
"symbol": "YUPIE",
"decimal": 18
},
{
"address": "0x558EC3152e2eb2174905cd19AeA4e34A23DE9aD6",
"symbol": "BRD",
"decimal": 18
},
{
"address": "0xaAAf91D9b90dF800Df4F55c205fd6989c977E73a",
"symbol": "TKN",
@ -2284,6 +2519,11 @@
"symbol": "BCPT",
"decimal": 18
},
{
"address": "0x9aeFBE0b3C3ba9Eab262CB9856E8157AB7648e09",
"symbol": "FLR",
"decimal": 18
},
{
"address": "0x0AF44e2784637218dD1D32A322D44e603A8f0c6A",
"symbol": "MTX",
@ -2399,6 +2639,11 @@
"symbol": "DROP",
"decimal": 0
},
{
"address": "0xF03f8D65BaFA598611C3495124093c56e8F638f0",
"symbol": "VIEW",
"decimal": 18
},
{
"address": "0x43F6a1BE992deE408721748490772B15143CE0a7",
"symbol": "POIN",
@ -2604,6 +2849,16 @@
"symbol": "WPR",
"decimal": 18
},
{
"address": "0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf",
"symbol": "GEN",
"decimal": 18
},
{
"address": "0xD760ADdFb24D9C01Fe4Bfea7475C5e3636684058",
"symbol": "USDM",
"decimal": 2
},
{
"address": "0xF4134146AF2d511Dd5EA8cDB1C4AC88C57D60404",
"symbol": "SNC",
@ -2644,11 +2899,6 @@
"symbol": "EMONT",
"decimal": 8
},
{
"address": "0xe9dE1C630753A15d7021Cc563429c21d4887506F",
"symbol": "OPEN",
"decimal": 8
},
{
"address": "0x514910771AF9Ca656af840dff83E8264EcF986CA",
"symbol": "LINK (Chainlink)",
@ -2899,6 +3149,11 @@
"symbol": "AIR",
"decimal": 8
},
{
"address": "0xF244176246168F24e3187f7288EdbCA29267739b",
"symbol": "HAV",
"decimal": 18
},
{
"address": "0xc8C6A31A4A806d3710A7B38b7B296D2fABCCDBA8",
"symbol": "ELIX",

View File

@ -28,9 +28,7 @@ import {
SecureSlideThree,
FinalSlide
} from './components';
const ONBOARD_LOCAL_STORAGE_KEY = 'onboardStatus';
const NUMBER_OF_SLIDES = 10;
import { ONBOARD_LOCAL_STORAGE_KEY, NUMBER_OF_ONBOARD_SLIDES } from 'utils/localStorage';
interface State {
isOpen: boolean;
@ -58,7 +56,6 @@ class OnboardModal extends React.Component<Props, State> {
public componentDidMount() {
const { sessionStarted } = this.props;
const currentSlide = Number(localStorage.getItem(ONBOARD_LOCAL_STORAGE_KEY)) || 0;
if (!sessionStarted) {
@ -68,7 +65,7 @@ class OnboardModal extends React.Component<Props, State> {
isOpen: true
});
}
if (currentSlide > 0 && currentSlide < NUMBER_OF_SLIDES) {
if (currentSlide > 0 && currentSlide < NUMBER_OF_ONBOARD_SLIDES) {
this.props.resumeSlide(currentSlide);
this.setState({
isOpen: true
@ -90,7 +87,7 @@ class OnboardModal extends React.Component<Props, State> {
const firstButtons: IButton[] = [
{
disabled: slideNumber === NUMBER_OF_SLIDES,
disabled: slideNumber === NUMBER_OF_ONBOARD_SLIDES,
text: translate('ACTION_6'),
type: 'primary',
onClick: this.handleNextSlide
@ -115,8 +112,8 @@ class OnboardModal extends React.Component<Props, State> {
}
];
const buttons = slideNumber === NUMBER_OF_SLIDES ? lastButtons : firstButtons;
const steps = new Array(NUMBER_OF_SLIDES).fill({});
const buttons = slideNumber === NUMBER_OF_ONBOARD_SLIDES ? lastButtons : firstButtons;
const steps = new Array(NUMBER_OF_ONBOARD_SLIDES).fill({});
return (
<div className="OnboardModal">
@ -158,8 +155,8 @@ class OnboardModal extends React.Component<Props, State> {
<FinalSlide key={10} closeModal={this.closeModal} />
];
if (slides.length !== NUMBER_OF_SLIDES) {
console.log('Slides length do not match const NUMBER_OF_SLIDES');
if (slides.length !== NUMBER_OF_ONBOARD_SLIDES) {
console.log('Slides length do not match const NUMBER_OF_ONBOARD_SLIDES');
}
const currentSlideIndex = this.props.slideNumber - 1;

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { BetaAgreement, Footer, Header } from 'components';
import { Footer, Header } from 'components';
import { AppState } from 'reducers';
import Notifications from './Notifications';
import OfflineTab from './OfflineTab';
@ -38,7 +38,6 @@ class WebTemplate extends Component<Props, {}> {
<div className="WebTemplate-spacer" />
<Footer latestBlock={latestBlock} />
<Notifications />
<BetaAgreement />
</div>
);
}

View File

@ -65,7 +65,7 @@ class BroadcastTx extends Component<Props> {
<Input
type="text"
placeholder="0xf86b0284ee6b2800825208944bbeeb066ed09b7aed07bf39eee0460dfa26152088016345785d8a00008029a03ba7a0cc6d1756cd771f2119cf688b6d4dc9d37096089f0331fe0de0d1cc1254a02f7bcd19854c8d46f8de09e457aec25b127ab4328e1c0d24bfbff8702ee1f474"
className={stateTransaction ? '' : 'invalid'}
isValid={!!stateTransaction}
value={userInput}
onChange={this.handleChange}
/>

View File

@ -33,7 +33,7 @@ class TxHashInput extends React.Component<Props, State> {
this.state = { hash: props.hash || '' };
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.hash !== nextProps.hash && nextProps.hash) {
this.setState({ hash: nextProps.hash });
}
@ -42,7 +42,7 @@ class TxHashInput extends React.Component<Props, State> {
public render() {
const { recentTxs } = this.props;
const { hash } = this.state;
const validClass = hash ? (isValidTxHash(hash) ? 'is-valid' : 'is-invalid') : '';
let selectOptions: Option[] = [];
if (recentTxs && recentTxs.length) {
@ -75,8 +75,9 @@ class TxHashInput extends React.Component<Props, State> {
<Input
value={hash}
isValid={hash ? isValidTxHash(hash) : true}
placeholder="0x16e521..."
className={`TxHashInput-field ${validClass}`}
className="TxHashInput-field"
onChange={this.handleChange}
/>

View File

@ -34,7 +34,7 @@ class CheckTransaction extends React.Component<Props, State> {
}
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
const { network } = this.props;
if (network.chainId !== nextProps.network.chainId) {
this.setState({ hash: '' });

View File

@ -1,5 +1,4 @@
import translate from 'translations';
import classnames from 'classnames';
import { DataFieldFactory } from 'components/DataFieldFactory';
import { SendButtonFactory } from 'components/SendButtonFactory';
import WalletDecrypt, { DISABLE_WALLETS } from 'components/WalletDecrypt';
@ -31,16 +30,15 @@ class DeployClass extends Component<DispatchProps> {
<label className="input-group">
<div className="input-group-header">{translate('CONTRACT_BYTECODE')}</div>
<DataFieldFactory
withProps={({ data: { raw, value }, onChange, readOnly }) => (
withProps={({ data: { raw }, onChange, readOnly, validData }) => (
<TextArea
isValid={validData && !!raw}
name="byteCode"
placeholder="0x8f87a973e..."
rows={6}
onChange={onChange}
disabled={readOnly}
className={classnames('Deploy-field-input', {
'is-valid': value && value.length > 0
})}
className="Deploy-field-input"
value={raw}
/>
)}

View File

@ -1,6 +1,6 @@
import { AmountFieldFactory } from 'components/AmountFieldFactory';
import React from 'react';
import classnames from 'classnames';
import { Input } from 'components/ui';
export const AmountField: React.SFC = () => (
@ -12,11 +12,10 @@ export const AmountField: React.SFC = () => (
<Input
name="value"
value={raw}
isValid={isValid || raw === ''}
onChange={onChange}
readOnly={readOnly}
className={classnames('InteractExplorer-field-input', 'form-control', {
'is-invalid': !(isValid || raw === '')
})}
className="InteractExplorer-field-input"
/>
)}
/>

View File

@ -116,6 +116,7 @@ class InteractExplorerClass extends Component<Props, State> {
<div className="input-group-header">{name + ' ' + type}</div>
<Input
className="InteractExplorer-func-in-input"
isValid={!!(inputs[name] && inputs[name].rawData)}
name={name}
value={(inputs[name] && inputs[name].rawData) || ''}
onChange={this.handleInputChange}
@ -138,7 +139,8 @@ class InteractExplorerClass extends Component<Props, State> {
<label className="input-group">
<div className="input-group-header"> {name + ' ' + type}</div>
<Input
className="InteractExplorer-func-out-input "
className="InteractExplorer-func-out-input"
isValid={!!decodedFieldValue}
value={decodedFieldValue}
disabled={true}
/>

View File

@ -4,7 +4,6 @@ import { getNetworkContracts } from 'selectors/config';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { isValidETHAddress, isValidAbiJson } from 'libs/validators';
import classnames from 'classnames';
import { NetworkContract } from 'types/network';
import { donationAddressMap } from 'config';
import { Input, TextArea, CodeBlock, Dropdown } from 'components/ui';
@ -63,7 +62,7 @@ class InteractForm extends Component<Props, State> {
};
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
const prevProps = this.props;
if (nextProps.currentTo.raw !== prevProps.currentTo.raw) {
nextProps.resetState();
@ -126,9 +125,8 @@ class InteractForm extends Component<Props, State> {
name="contract_address"
autoComplete="off"
value={currentTo.raw}
className={classnames('InteractForm-address-field-input', {
invalid: !isValid
})}
isValid={isValid}
className="InteractForm-address-field-input"
spellCheck={false}
onChange={onChange}
/>
@ -144,7 +142,8 @@ class InteractForm extends Component<Props, State> {
contract.name === 'Custom' ? (
<TextArea
placeholder={this.abiJsonPlaceholder}
className={`InteractForm-interface-field-input ${validAbiJson ? '' : 'invalid'}`}
isValid={!!validAbiJson}
className="InteractForm-interface-field-input"
onChange={this.handleInput('abiJson')}
value={abiJson}
rows={6}
@ -155,7 +154,8 @@ class InteractForm extends Component<Props, State> {
) : (
<TextArea
placeholder={this.abiJsonPlaceholder}
className={`InteractForm-interface-field-input ${validAbiJson ? '' : 'invalid'}`}
isValid={!!validAbiJson}
className="InteractForm-interface-field-input"
onChange={this.handleInput('abiJson')}
value={abiJson}
rows={6}

View File

@ -37,9 +37,8 @@ class NameInput extends Component<Props, State> {
<label className="input-group input-group-inline ENSInput-name">
<Input
value={domainToCheck}
className={`${
!domainToCheck ? '' : isValidDomain ? '' : 'invalid'
} border-rad-right-0`}
isValid={!!domainToCheck && isValidDomain}
className="border-rad-right-0"
type="text"
placeholder="mycrypto"
onChange={this.onChange}

View File

@ -21,6 +21,8 @@ const PaperWallet: React.SFC<Props> = props => (
<h1 className="GenPaper-title">{translate('GEN_LABEL_5')}</h1>
<Input
value={stripHexPrefix(props.privateKey)}
showValidAsPlain={true}
isValid={true}
aria-label={translateRaw('X_PRIVKEY')}
aria-describedby="x_PrivKeyDesc"
type="text"

View File

@ -61,7 +61,13 @@ export default class MnemonicWord extends React.Component<Props, State> {
{word}
</button>
) : (
<Input className="MnemonicWord-word-input" value={word} readOnly={true} />
<Input
className="MnemonicWord-word-input"
value={word}
readOnly={true}
showValidAsPlain={true}
isValid={true}
/>
)}
</label>
</div>

View File

@ -38,7 +38,7 @@ class ScheduleDepositFieldClass extends Component<Props> {
</span>
</div>
<Input
className={!!scheduleDeposit.raw && !validScheduleDeposit ? 'invalid' : ''}
isValid={scheduleDeposit.raw && validScheduleDeposit}
type="number"
placeholder="0.00001"
value={scheduleDeposit.raw}

View File

@ -34,7 +34,7 @@ class ScheduleGasLimitFieldClass extends React.Component<Props> {
<InlineSpinner active={gasEstimationPending} text="Calculating" />
</div>
<Input
className={!!scheduleGasLimit.raw && !validScheduleGasLimit ? 'invalid' : ''}
isValid={scheduleGasLimit.raw && validScheduleGasLimit}
type="number"
placeholder={EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_LIMIT_FALLBACK.toString()}
value={scheduleGasLimit.raw}

View File

@ -27,7 +27,7 @@ class ScheduleGasPriceFieldClass extends React.Component<Props> {
<label className="input-group">
<div className="input-group-header">{translateRaw('SCHEDULE_GAS_PRICE')} (gwei)</div>
<Input
className={!!scheduleGasPrice.raw && !validScheduleGasPrice ? 'invalid' : ''}
isValid={scheduleGasPrice.raw && validScheduleGasPrice}
type="number"
placeholder="40"
value={scheduleGasPrice.raw}

View File

@ -25,7 +25,7 @@ export const TimeBountyField: React.SFC<Props> = ({ isReadOnly }) => (
</span>
</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={currentTimeBounty.raw}
placeholder={translateRaw('SCHEDULE_TIMEBOUNTY_PLACEHOLDER')}

View File

@ -32,7 +32,7 @@ export const WindowSizeField: React.SFC<Props> = ({ isReadOnly }) => (
</span>
</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={currentWindowSize.raw}
placeholder={

View File

@ -14,7 +14,7 @@ export const WindowStartField: React.SFC<Props> = ({ isReadOnly }) => (
<label className="input-group">
<div className="input-group-header">{translate('SCHEDULE_BLOCK')}</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={currentWindowStart.raw}
placeholder={translateRaw('SCHEDULE_BLOCK_PLACEHOLDER')}

View File

@ -68,7 +68,7 @@ class RequestPayment extends React.Component<Props, {}> {
this.props.resetTransactionRequested();
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.wallet && this.props.wallet !== nextProps.wallet) {
this.setWalletAsyncState(nextProps.wallet);
}

View File

@ -30,7 +30,7 @@ export default class WalletInfo extends React.PureComponent<Props, State> {
this.setStateFromWallet(this.props.wallet);
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.wallet !== nextProps.wallet) {
this.setStateFromWallet(nextProps.wallet);
}

View File

@ -57,7 +57,8 @@ export class SignMessage extends Component<Props, State> {
<label className="input-group">
<div className="input-group-header">{translate('MSG_MESSAGE')}</div>
<TextArea
className={`SignMessage-inputBox ${message ? 'is-valid' : 'is-invalid'}`}
isValid={!!message}
className="SignMessage-inputBox"
placeholder={messagePlaceholder}
value={message}
onChange={this.handleMessageChange}

View File

@ -41,7 +41,8 @@ export class VerifyMessage extends Component<Props, State> {
<label className="input-group">
<div className="input-group-header">{translate('MSG_SIGNATURE')}</div>
<TextArea
className={`VerifyMessage-inputBox ${signature ? 'is-valid' : 'is-invalid'}`}
isValid={!!signature}
className="VerifyMessage-inputBox"
placeholder={signaturePlaceholder}
value={signature}
onChange={this.handleSignatureChange}

View File

@ -2,7 +2,13 @@ import React from 'react';
import translate from 'translations';
import TabSection from 'containers/TabSection';
import logo from 'assets/images/logo-mycrypto-transparent.svg';
import { donationAddressMap, socialMediaLinks, productLinks, affiliateLinks } from 'config';
import {
donationAddressMap,
socialMediaLinks,
productLinks,
affiliateLinks,
VERSION
} from 'config';
import DisclaimerModal from 'components/DisclaimerModal';
import { NewTabLink } from 'components/ui';
import './index.scss';
@ -47,6 +53,7 @@ export default class SupportPage extends React.Component<{}, State> {
<div className="SupportPage-mycrypto-legal-text">
<a onClick={this.openDisclaimer}>{translate('DISCLAIMER')}</a>
</div>
<div className="SupportPage-mycrypto-legal-text">v{VERSION}</div>
</div>
</div>
</div>

View File

@ -117,7 +117,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
}
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.options !== this.props.options) {
this.setState({ options: Object.values(nextProps.options.byId) });
}
@ -316,12 +316,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
<div className="input-group input-group-inline">
<Input
id="origin-swap-input"
className={`input-group-input ${
!!origin.amount &&
this.isMinMaxValid(origin.amount, origin.label, destination.label)
? ''
: 'invalid'
}`}
isValid={!originErr}
type="number"
placeholder={translateRaw('SEND_AMOUNT_SHORT')}
value={isNaN(origin.amount) ? '' : origin.amount}
@ -341,12 +336,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
<div className="input-group-header">{translate('SWAP_RECEIVE_INPUT_LABEL')}</div>
<Input
id="destination-swap-input"
className={`${
!!destination.amount &&
this.isMinMaxValid(origin.amount, origin.label, destination.label)
? ''
: 'invalid'
}`}
isValid={!destinationErr}
type="number"
placeholder={translateRaw('SEND_AMOUNT_SHORT')}
value={isNaN(destination.amount) ? '' : destination.amount}

View File

@ -49,7 +49,7 @@ class CurrentRates extends PureComponent<Props> {
}
}
public componentWillReceiveProps(nextProps: Props) {
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.isOffline && !nextProps.isOffline) {
this.loadRates();
}

View File

@ -37,7 +37,12 @@ class FieldsClass extends Component<Props> {
<div className="col-xs-12">
<AddressFieldFactory
withProps={({ currentTo }) => (
<Input type="text" value={currentTo.raw} readOnly={true} />
<Input
type="text"
value={currentTo.raw}
readOnly={true}
isValid={!!currentTo.raw}
/>
)}
/>
</div>
@ -60,6 +65,8 @@ class FieldsClass extends Component<Props> {
)}
{isValid && (
<Input
isValid={true}
showValidAsPlain={true}
type="text"
value={`${currentValue.raw} ${this.props.unit}`}
readOnly={true}

View File

@ -21,6 +21,7 @@ export default class PaymentInfo extends PureComponent<Props, {}> {
})}
<Input
className="SwapPayment-address"
isValid={!!this.props.paymentAddress}
value={this.props.paymentAddress || undefined}
disabled={true}
/>

View File

@ -79,7 +79,8 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
</h4>
<Input
className={`SwapAddress-address-input ${!validAddress ? 'invalid' : ''}`}
isValid={validAddress}
className="SwapAddress-address-input"
type="text"
value={destinationAddress}
onChange={this.onChangeDestinationAddress}

View File

@ -83,7 +83,13 @@ Rate: ${rates[pair].rate} ${origin.label}/${destination.label}`;
<small>{translate('SWAP_SUPPORT_LINK_BROKEN')}</small>
</p>
{open ? (
<TextArea defaultValue={fallbackBody} className="form-control input-sm" rows={9} />
<TextArea
isValid={true}
showValidAsPlain={true}
defaultValue={fallbackBody}
className="input-sm"
rows={9}
/>
) : null}
</div>
</section>

View File

@ -5,8 +5,16 @@
<meta charset="utf-8">
<title>MyCrypto</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="description" content="">
<meta name="description" content="MyCrypto is a free, open-source interface for interacting with the blockchain.">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta property="og:title" content="<%= htmlWebpackPlugin.options.title %>">
<meta property="og:url" content="<%= htmlWebpackPlugin.options.appUrl %>">
<meta property="og:description" content="<%= htmlWebpackPlugin.options.appDescription %>">
<meta property="og:site_name" content="<%= htmlWebpackPlugin.options.title %>">
<meta property="og:type" content="<%= htmlWebpackPlugin.options.type %>">
<meta property="og:image" content="<%= htmlWebpackPlugin.options.image %>">
<meta name="twitter:site" content="<%= htmlWebpackPlugin.options.twitter.site %>">
<meta name="twitter:creator" content="<%= htmlWebpackPlugin.options.twitter.creator %>">
<link rel="manifest" href="/manifest.json">
</head>
@ -32,16 +40,17 @@
<div class="BadBrowser-content">
<h2>Your Browser is Out of Date</h2>
<p class="is-desktop">
MyCrypto requires certain features that your browser doesn't offer. Your browser may also be missing security updates
that could open you up to vulnerabilities. Please update your browser, or switch to one of the following browsers
to continue using MyCrypto.
MyCrypto requires certain features that your browser doesn't offer. Your browser may also be missing security updates that
could open you up to vulnerabilities. Please update your browser, or switch to one of the following browsers to continue
using MyCrypto.
</p>
<p class="is-mobile">
MyCrypto requires certain features that your browser doesn't offer. Please use your device's default browser, or switch
to a laptop or desktop computer to continue using MyCrypto.
MyCrypto requires certain features that your browser doesn't offer. Please use your device's default browser, or switch to
a laptop or desktop computer to continue using MyCrypto.
</p>
<div class="BadBrowser-content-browsers is-desktop">
<a class="BadBrowser-content-browsers-browser firefox" href="https://www.mozilla.org/en-US/firefox/new/" rel="noopener noreferrer" target="_blank">
<a class="BadBrowser-content-browsers-browser firefox" href="https://www.mozilla.org/en-US/firefox/new/" rel="noopener noreferrer"
target="_blank">
<span class="BadBrowser-content-browsers-browser-name">
Firefox
</span>
@ -97,4 +106,4 @@
</script>
</body>
</html>
</html>

View File

@ -95,8 +95,12 @@ export default class AbiFunction {
return mapppedType ? mapppedType(value) : BN.isBN(value) ? value.toString() : value;
};
private parsePreEncodedValue = (_: string, value: any) =>
BN.isBN(value) ? value.toString() : value;
private parsePreEncodedValue = (type: string, value: any) => {
if (type === 'bytes') {
return Buffer.from(value, 'hex');
}
return BN.isBN(value) ? value.toString() : value;
};
private makeFuncParams = () =>
this.inputs.reduce((accumulator, currInput) => {

View File

@ -11,7 +11,7 @@ export const makeProviderConfig = (options: DeepPartial<IProviderConfig> = {}):
const defaultConfig: IProviderConfig = {
concurrency: 2,
network: 'ETH',
requestFailureThreshold: 3,
requestFailureThreshold: 10,
supportedMethods: {
getNetVersion: true,
ping: true,
@ -30,7 +30,7 @@ export const makeProviderConfig = (options: DeepPartial<IProviderConfig> = {}):
signMessage: true,
sendTransaction: true
},
timeoutThresholdMs: 5000
timeoutThresholdMs: 10000
};
return {
@ -45,7 +45,7 @@ export const makeProviderConfig = (options: DeepPartial<IProviderConfig> = {}):
let shepherdProvider: INode;
shepherd
.init()
.init({ queueTimeout: 10000 })
.then(
provider => (shepherdProvider = (new Proxy(provider, tokenBalanceHandler) as any) as INode)
);

View File

@ -8,11 +8,10 @@ interface ABIFunc<T, K = void> {
type address = any;
type uint256 = any;
type bytes = any;
interface IRequestFactory {
validateRequestParams: ABIFunc<
{ _addressArgs: address[]; _uintArgs: uint256[]; _callData: bytes; _endowment: uint256 },
{ _addressArgs: address[]; _uintArgs: uint256[]; _endowment: uint256 },
{ paramsValidity: boolean[] }
>;
}
@ -29,10 +28,6 @@ const requestFactoryAbi = [
name: '_uintArgs',
type: 'uint256[12]'
},
{
name: '_callData',
type: 'bytes'
},
{
name: '_endowment',
type: 'uint256'

View File

@ -26,9 +26,9 @@ export const EAC_SCHEDULING_CONFIG = {
export const EAC_ADDRESSES = {
KOVAN: {
blockScheduler: '0x1afc19a7e642761ba2b55d2a45b32c7ef08269d1',
requestFactory: '0x496e2b6089bde77293a994469b08e9f266d87adb',
timestampScheduler: '0xc6370807f0164bdf10a66c08d0dab1028dbe80a3'
blockScheduler: '0x394ce9fe06c72f18e5a845842974f0c1224b1ff5',
requestFactory: '0x98c128b3d8a0ac240f7b7dd4969ea0ad54f9d330',
timestampScheduler: '0x31bbbf5180f2bd9c213e2e1d91a439677243268a'
}
};
@ -163,7 +163,6 @@ export const parseSchedulingParametersValidity = (isValid: boolean[]) => {
export const getValidateRequestParamsData = (
toAddress: string,
callData = '',
callGas: Wei,
callValue: ICurrentValue['value'],
windowSize: BN | null,
@ -200,7 +199,6 @@ export const getValidateRequestParamsData = (
gasPrice,
requiredDeposit
],
_callData: callData,
_endowment: endowment
});
};

Some files were not shown because too many files have changed in this diff Show More