Merge develop, resolve conflicts.
This commit is contained in:
commit
2b5b4848a8
|
@ -8,6 +8,10 @@
|
|||
"beta": {
|
||||
"gitUrl": "git@github.com:MyCryptoHQ/MyCrypto-Beta.git",
|
||||
"distFolder": "docs"
|
||||
},
|
||||
"prod": {
|
||||
"gitUrl": "git@github.com:MyCryptoHQ/MyCrypto-Production.git",
|
||||
"distFolder": "docs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
70
.travis.yml
70
.travis.yml
|
@ -1,35 +1,65 @@
|
|||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
language: node_js
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode9.3
|
||||
env:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
services: docker
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
|
||||
services:
|
||||
- docker
|
||||
before_cache:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
rm -rf $HOME/.cache/electron-builder/wine
|
||||
fi
|
||||
|
||||
before_install:
|
||||
- export CHROME_BIN=chromium-browser
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
# uncomment once integration tests are included in CI
|
||||
# - docker pull dternyak/eth-priv-to-addr:latest
|
||||
- sudo apt-get install libusb-1.0
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
export CHROME_BIN=chromium-browser
|
||||
export DISPLAY=:99.0
|
||||
sh -e /etc/init.d/xvfb start
|
||||
# uncomment once integration tests are included in CI
|
||||
# docker pull dternyak/eth-priv-to-addr:latest
|
||||
sudo apt-get install libusb-1.0
|
||||
fi
|
||||
|
||||
install:
|
||||
- npm install --silent
|
||||
- yarn --silent
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
script: npm run prettier:diff
|
||||
- stage: test
|
||||
script: npm run test:coverage -- --maxWorkers=2 && npm run report-coverage
|
||||
- stage: test
|
||||
script: npm run tslint && npm run tscheck && npm run freezer && npm run freezer:validate
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
npm run prettier:diff
|
||||
npm run test:coverage -- --maxWorkers=2
|
||||
npm run report-coverage
|
||||
npm run tslint
|
||||
npm run tscheck
|
||||
npm run freezer
|
||||
npm run freezer:validate
|
||||
fi
|
||||
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
npm run build:electron
|
||||
ls -la dist/electron-builds
|
||||
fi
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: never
|
||||
on_failure: never
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
# 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 +134,4 @@ npm run test:int
|
|||
</a>
|
||||
|
||||
Cross browser testing and debugging provided by the very lovely team at BrowserStack.
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { TypeKeys } from './constants';
|
||||
import {
|
||||
AddressLabel,
|
||||
AddressLabelEntry,
|
||||
SetAddressLabel,
|
||||
ClearAddressLabel,
|
||||
SetAddressLabelEntry,
|
||||
ChangeAddressLabelEntry,
|
||||
SaveAddressLabelEntry,
|
||||
ClearAddressLabelEntry,
|
||||
RemoveAddressLabelEntry
|
||||
} from './actionTypes';
|
||||
|
||||
export type TSetAddressLabel = typeof setAddressLabel;
|
||||
export function setAddressLabel(payload: AddressLabel): SetAddressLabel {
|
||||
return {
|
||||
type: TypeKeys.SET_ADDRESS_LABEL,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TClearAddressLabel = typeof clearAddressLabel;
|
||||
export function clearAddressLabel(payload: string): ClearAddressLabel {
|
||||
return {
|
||||
type: TypeKeys.CLEAR_ADDRESS_LABEL,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TSetAddressLabelEntry = typeof setAddressLabelEntry;
|
||||
export function setAddressLabelEntry(payload: AddressLabelEntry): SetAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.SET_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TChangeAddressLabelEntry = typeof changeAddressLabelEntry;
|
||||
export function changeAddressLabelEntry(payload: AddressLabelEntry): ChangeAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.CHANGE_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TSaveAddressLabelEntry = typeof saveAddressLabelEntry;
|
||||
export function saveAddressLabelEntry(payload: string): SaveAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.SAVE_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TClearAddressLabelEntry = typeof clearAddressLabelEntry;
|
||||
export function clearAddressLabelEntry(payload: string): ClearAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.CLEAR_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TRemoveAddressLabelEntry = typeof removeAddressLabelEntry;
|
||||
export function removeAddressLabelEntry(payload: string): RemoveAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.REMOVE_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
};
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { TypeKeys } from './constants';
|
||||
|
||||
export interface AddressLabel {
|
||||
address: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface AddressLabelEntry extends AddressLabel {
|
||||
id: string;
|
||||
temporaryAddress?: string;
|
||||
addressError?: string;
|
||||
temporaryLabel?: string;
|
||||
labelError?: string;
|
||||
isEditing?: boolean;
|
||||
overrideValidation?: boolean;
|
||||
}
|
||||
|
||||
export interface SetAddressLabel {
|
||||
type: TypeKeys.SET_ADDRESS_LABEL;
|
||||
payload: AddressLabel;
|
||||
}
|
||||
|
||||
export interface ClearAddressLabel {
|
||||
type: TypeKeys.CLEAR_ADDRESS_LABEL;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface SetAddressLabelEntry {
|
||||
type: TypeKeys.SET_ADDRESS_LABEL_ENTRY;
|
||||
payload: AddressLabelEntry;
|
||||
}
|
||||
|
||||
export interface ChangeAddressLabelEntry {
|
||||
type: TypeKeys.CHANGE_ADDRESS_LABEL_ENTRY;
|
||||
payload: AddressLabelEntry;
|
||||
}
|
||||
|
||||
export interface SaveAddressLabelEntry {
|
||||
type: TypeKeys.SAVE_ADDRESS_LABEL_ENTRY;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface ClearAddressLabelEntry {
|
||||
type: TypeKeys.CLEAR_ADDRESS_LABEL_ENTRY;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface RemoveAddressLabelEntry {
|
||||
type: TypeKeys.REMOVE_ADDRESS_LABEL_ENTRY;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export type AddressBookAction =
|
||||
| SetAddressLabel
|
||||
| ClearAddressLabel
|
||||
| SetAddressLabelEntry
|
||||
| ChangeAddressLabelEntry
|
||||
| SaveAddressLabelEntry
|
||||
| ClearAddressLabelEntry
|
||||
| RemoveAddressLabelEntry;
|
|
@ -0,0 +1,9 @@
|
|||
export enum TypeKeys {
|
||||
SET_ADDRESS_LABEL = 'SET_ADDRESS_LABEL',
|
||||
CLEAR_ADDRESS_LABEL = 'CLEAR_ADDRESS_LABEL',
|
||||
SET_ADDRESS_LABEL_ENTRY = 'SET_ADDRESS_LABEL_TEMPORARY_ENTRY',
|
||||
CHANGE_ADDRESS_LABEL_ENTRY = 'CHANGE_ADDRESS_LABEL_ENTRY',
|
||||
SAVE_ADDRESS_LABEL_ENTRY = 'SAVE_ADDRESS_LABEL_ENTRY',
|
||||
CLEAR_ADDRESS_LABEL_ENTRY = 'CLEAR_ADDRESS_LABEL_ENTRY',
|
||||
REMOVE_ADDRESS_LABEL_ENTRY = 'REMOVE_ADDRESS_LABEL_ENTRY'
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './actionTypes';
|
||||
export * from './actionCreators';
|
||||
export * from './constants';
|
|
@ -47,11 +47,11 @@ export const rateSymbols: IRateSymbols = {
|
|||
|
||||
// TODO - internationalize
|
||||
const ERROR_MESSAGE = 'Could not fetch rate data.';
|
||||
const CCApi = 'https://min-api.cryptocompare.com';
|
||||
const CCApi = 'https://proxy.mycryptoapi.com/cc';
|
||||
|
||||
const CCRates = (symbols: string[]) => {
|
||||
const tsyms = rateSymbols.symbols.all.concat(symbols as any).join(',');
|
||||
return `${CCApi}/data/price?fsym=ETH&tsyms=${tsyms}`;
|
||||
return `${CCApi}/price?fsym=ETH&tsyms=${tsyms}`;
|
||||
};
|
||||
|
||||
export interface CCResponse {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
|
@ -0,0 +1,139 @@
|
|||
@import 'common/sass/variables/spacing';
|
||||
@import 'common/sass/variables/colors';
|
||||
|
||||
.AddressBookTable {
|
||||
&-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $space-md;
|
||||
|
||||
@media (max-width: 650px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0.1), 0 1px 4px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
&-label {
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
&-error {
|
||||
align-items: flex-end;
|
||||
align-self: stretch;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
color: $brand-danger;
|
||||
|
||||
&-temporary-input--non-mobile {
|
||||
flex: 1;
|
||||
visibility: hidden;
|
||||
|
||||
&-address {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
&-label {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--non-mobile {
|
||||
justify-content: flex-end;
|
||||
|
||||
@media (max-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--mobile {
|
||||
@media (min-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-identicon {
|
||||
margin-left: $space-sm;
|
||||
|
||||
&-mobile {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: $space-md;
|
||||
|
||||
@media (min-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-non-mobile {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
|
||||
@media (max-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-labels {
|
||||
@media (max-width: 650px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
align-self: stretch;
|
||||
flex: 1;
|
||||
margin-right: $space-sm;
|
||||
|
||||
@media (max-width: 650px) {
|
||||
margin-right: 0;
|
||||
margin-bottom: $space-md;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: $space-sm;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-left: $space-sm;
|
||||
height: $space-lg * 2;
|
||||
|
||||
@media (max-width: 650px) {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
flex: 1;
|
||||
|
||||
&-label {
|
||||
@media (min-width: 651px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
color: $brand-danger;
|
||||
align-self: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.is-visible {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
import React from 'react';
|
||||
import { connect, MapStateToProps } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
import { AppState } from 'reducers';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import {
|
||||
changeAddressLabelEntry,
|
||||
TChangeAddressLabelEntry,
|
||||
saveAddressLabelEntry,
|
||||
TSaveAddressLabelEntry,
|
||||
removeAddressLabelEntry,
|
||||
TRemoveAddressLabelEntry
|
||||
} from 'actions/addressBook';
|
||||
import {
|
||||
getAddressLabels,
|
||||
getLabelAddresses,
|
||||
getAddressLabelRows,
|
||||
getAddressBookTableEntry
|
||||
} from 'selectors/addressBook';
|
||||
import { Input, Identicon } from 'components/ui';
|
||||
import AddressBookTableRow from './AddressBookTableRow';
|
||||
import './AddressBookTable.scss';
|
||||
|
||||
interface DispatchProps {
|
||||
changeAddressLabelEntry: TChangeAddressLabelEntry;
|
||||
saveAddressLabelEntry: TSaveAddressLabelEntry;
|
||||
removeAddressLabelEntry: TRemoveAddressLabelEntry;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
rows: ReturnType<typeof getAddressLabelRows>;
|
||||
entry: ReturnType<typeof getAddressBookTableEntry>;
|
||||
addressLabels: ReturnType<typeof getAddressLabels>;
|
||||
labelAddresses: ReturnType<typeof getLabelAddresses>;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & StateProps;
|
||||
|
||||
interface State {
|
||||
editingRow: number | null;
|
||||
addressTouched: boolean;
|
||||
addressBlurred: boolean;
|
||||
labelTouched: boolean;
|
||||
labelBlurred: boolean;
|
||||
}
|
||||
|
||||
export const ADDRESS_BOOK_TABLE_ID: string = 'ADDRESS_BOOK_TABLE_ID';
|
||||
|
||||
class AddressBookTable extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
editingRow: null,
|
||||
addressTouched: false,
|
||||
addressBlurred: false,
|
||||
labelTouched: false,
|
||||
labelBlurred: false
|
||||
};
|
||||
|
||||
private addressInput: HTMLInputElement | null = null;
|
||||
|
||||
private labelInput: HTMLInputElement | null = null;
|
||||
|
||||
public render() {
|
||||
const {
|
||||
entry: { temporaryAddress = '', addressError = '', temporaryLabel = '', labelError = '' },
|
||||
rows
|
||||
} = this.props;
|
||||
const { addressTouched, addressBlurred, labelTouched, labelBlurred } = this.state;
|
||||
|
||||
// Classnames
|
||||
const addressTouchedWithError = addressTouched && addressError;
|
||||
const labelTouchedWithError = labelTouched && labelError;
|
||||
const nonMobileTemporaryInputErrorClassName =
|
||||
'AddressBookTable-row-error-temporary-input--non-mobile';
|
||||
|
||||
const nonMobileTemporaryAddressErrorClassName = classnames({
|
||||
[nonMobileTemporaryInputErrorClassName]: true,
|
||||
[`${nonMobileTemporaryInputErrorClassName}-address`]: true,
|
||||
'is-visible': !!addressTouchedWithError
|
||||
});
|
||||
|
||||
const nonMobileTemporaryLabelErrorClassName = classnames({
|
||||
[nonMobileTemporaryInputErrorClassName]: true,
|
||||
[`${nonMobileTemporaryInputErrorClassName}-label`]: true,
|
||||
'is-visible': !!labelTouchedWithError
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="AddressBookTable" onKeyDown={this.handleKeyDown}>
|
||||
<div className="AddressBookTable-row AddressBookTable-row-labels">
|
||||
<label className="AddressBookTable-row-label" htmlFor="temporaryAddress">
|
||||
{translate('ADDRESS')}
|
||||
</label>
|
||||
<label className="AddressBookTable-row-label" htmlFor="temporaryLabel">
|
||||
{translate('LABEL')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="AddressBookTable-row AddressBookTable-row-inputs">
|
||||
<div className="AddressBookTable-row-input">
|
||||
<div className="AddressBookTable-row-input-wrapper">
|
||||
<label
|
||||
className="AddressBookTable-row-input-wrapper-label"
|
||||
htmlFor="temporaryAddress"
|
||||
>
|
||||
{translate('ADDRESS')}
|
||||
</label>
|
||||
<Input
|
||||
name="temporaryAddress"
|
||||
placeholder={translateRaw('NEW_ADDRESS')}
|
||||
value={temporaryAddress}
|
||||
onChange={this.handleAddressChange}
|
||||
onFocus={this.setAddressTouched}
|
||||
onBlur={this.setAddressBlurred}
|
||||
setInnerRef={this.setAddressInputRef}
|
||||
isValid={!addressTouchedWithError}
|
||||
/>
|
||||
</div>
|
||||
<div className="AddressBookTable-row-identicon AddressBookTable-row-identicon-non-mobile">
|
||||
<Identicon address={temporaryAddress} />
|
||||
</div>
|
||||
<div className="AddressBookTable-row-identicon AddressBookTable-row-identicon-mobile">
|
||||
<Identicon address={temporaryAddress} size="3rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="AddressBookTable-row AddressBookTable-row-error AddressBookTable-row-error--mobile">
|
||||
<label className="AddressBookTable-row-input-wrapper-error">
|
||||
{addressBlurred && addressError}
|
||||
</label>
|
||||
</div>
|
||||
<div className="AddressBookTable-row-input">
|
||||
<div className="AddressBookTable-row-input-wrapper">
|
||||
<label className="AddressBookTable-row-input-wrapper-label" htmlFor="temporaryLabel">
|
||||
{translate('LABEL')}
|
||||
</label>
|
||||
<Input
|
||||
name="temporaryLabel"
|
||||
placeholder={translateRaw('NEW_LABEL')}
|
||||
value={temporaryLabel}
|
||||
onChange={this.handleLabelChange}
|
||||
onFocus={this.setLabelTouched}
|
||||
onBlur={this.setLabelBlurred}
|
||||
setInnerRef={this.setLabelInputRef}
|
||||
isValid={!labelTouchedWithError}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
title={translateRaw('ADD_LABEL')}
|
||||
className="btn btn-sm btn-success"
|
||||
onClick={this.handleAddEntry}
|
||||
>
|
||||
<i className="fa fa-plus" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="AddressBookTable-row AddressBookTable-row-error AddressBookTable-row-error--mobile">
|
||||
<label className="AddressBookTable-row-input-wrapper-error">
|
||||
{labelBlurred && labelError}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="AddressBookTable-row AddressBookTable-row-error">
|
||||
<label className={nonMobileTemporaryAddressErrorClassName}>
|
||||
{addressBlurred && addressError}
|
||||
</label>
|
||||
<label className={nonMobileTemporaryLabelErrorClassName}>
|
||||
{labelBlurred && labelError}
|
||||
</label>
|
||||
</div>
|
||||
{rows.map(this.makeLabelRow)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
private handleAddEntry = () => {
|
||||
const { entry: { temporaryAddress, addressError, labelError } } = this.props;
|
||||
|
||||
if (!temporaryAddress || addressError || temporaryAddress.length === 0) {
|
||||
return this.addressInput && this.addressInput.focus();
|
||||
}
|
||||
|
||||
if (labelError && this.labelInput) {
|
||||
this.labelInput.focus();
|
||||
}
|
||||
|
||||
this.props.saveAddressLabelEntry(ADDRESS_BOOK_TABLE_ID);
|
||||
|
||||
if (!addressError && !labelError) {
|
||||
this.clearFieldStatuses();
|
||||
this.setEditingRow(null);
|
||||
}
|
||||
};
|
||||
|
||||
private handleKeyDown = (e: React.KeyboardEvent<HTMLTableElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleAddEntry();
|
||||
}
|
||||
};
|
||||
|
||||
private setEditingRow = (editingRow: number | null) => this.setState({ editingRow });
|
||||
|
||||
private clearEditingRow = () => this.setEditingRow(null);
|
||||
|
||||
private makeLabelRow = (row: any, index: number) => {
|
||||
const { editingRow } = this.state;
|
||||
const { id, address, label, temporaryLabel, labelError } = row;
|
||||
const isEditing = index === editingRow;
|
||||
const onChange = (newLabel: string) =>
|
||||
this.props.changeAddressLabelEntry({
|
||||
id,
|
||||
address,
|
||||
label: newLabel,
|
||||
isEditing: true
|
||||
});
|
||||
const onSave = () => {
|
||||
this.props.saveAddressLabelEntry(id);
|
||||
this.setEditingRow(null);
|
||||
};
|
||||
const onLabelInputBlur = () => {
|
||||
// If the new changes aren't valid, undo them.
|
||||
if (labelError) {
|
||||
this.props.changeAddressLabelEntry({
|
||||
id,
|
||||
address,
|
||||
temporaryAddress: address,
|
||||
label,
|
||||
temporaryLabel: label,
|
||||
overrideValidation: true
|
||||
});
|
||||
}
|
||||
|
||||
this.clearEditingRow();
|
||||
};
|
||||
|
||||
return (
|
||||
<AddressBookTableRow
|
||||
key={address}
|
||||
index={index}
|
||||
address={address}
|
||||
label={label}
|
||||
temporaryLabel={temporaryLabel}
|
||||
labelError={labelError}
|
||||
isEditing={isEditing}
|
||||
onChange={onChange}
|
||||
onSave={onSave}
|
||||
onLabelInputBlur={onLabelInputBlur}
|
||||
onEditClick={() => this.setEditingRow(index)}
|
||||
onRemoveClick={() => this.props.removeAddressLabelEntry(id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
private setAddressInputRef = (node: HTMLInputElement) => (this.addressInput = node);
|
||||
|
||||
private setAddressTouched = () =>
|
||||
!this.state.addressTouched && this.setState({ addressTouched: true });
|
||||
|
||||
private clearAddressTouched = () => this.setState({ addressTouched: false });
|
||||
|
||||
private setAddressBlurred = () => this.setState({ addressBlurred: true });
|
||||
|
||||
private handleAddressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { entry } = this.props;
|
||||
const address = e.target.value;
|
||||
const label = entry.temporaryLabel || '';
|
||||
|
||||
this.props.changeAddressLabelEntry({
|
||||
id: ADDRESS_BOOK_TABLE_ID,
|
||||
address,
|
||||
label
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{ addressTouched: true },
|
||||
() => address.length === 0 && this.clearAddressTouched()
|
||||
);
|
||||
};
|
||||
|
||||
private setLabelInputRef = (node: HTMLInputElement) => (this.labelInput = node);
|
||||
|
||||
private setLabelTouched = () => !this.state.labelTouched && this.setState({ labelTouched: true });
|
||||
|
||||
private clearLabelTouched = () => this.setState({ labelTouched: false });
|
||||
|
||||
private setLabelBlurred = () => this.setState({ labelBlurred: true });
|
||||
|
||||
private handleLabelChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { entry } = this.props;
|
||||
const address = entry.temporaryAddress || '';
|
||||
const label = e.target.value;
|
||||
|
||||
this.props.changeAddressLabelEntry({
|
||||
id: ADDRESS_BOOK_TABLE_ID,
|
||||
address,
|
||||
label
|
||||
});
|
||||
|
||||
this.setState({ labelTouched: true }, () => label.length === 0 && this.clearLabelTouched());
|
||||
};
|
||||
|
||||
private clearFieldStatuses = () =>
|
||||
this.setState({
|
||||
addressTouched: false,
|
||||
addressBlurred: false,
|
||||
labelTouched: false,
|
||||
labelBlurred: false
|
||||
});
|
||||
}
|
||||
|
||||
const mapStateToProps: MapStateToProps<StateProps, {}, AppState> = state => ({
|
||||
rows: getAddressLabelRows(state),
|
||||
entry: getAddressBookTableEntry(state),
|
||||
addressLabels: getAddressLabels(state),
|
||||
labelAddresses: getLabelAddresses(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps: DispatchProps = {
|
||||
changeAddressLabelEntry,
|
||||
saveAddressLabelEntry,
|
||||
removeAddressLabelEntry
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AddressBookTable);
|
|
@ -0,0 +1,150 @@
|
|||
import React from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import noop from 'lodash/noop';
|
||||
import { Input, Identicon } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
address: string;
|
||||
label: string;
|
||||
temporaryLabel: string;
|
||||
labelError?: string;
|
||||
isEditing: boolean;
|
||||
onChange(label: string): void;
|
||||
onSave(): void;
|
||||
onLabelInputBlur(): void;
|
||||
onEditClick(): void;
|
||||
onRemoveClick(): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
labelInputTouched: boolean;
|
||||
}
|
||||
|
||||
class AddressBookTableRow extends React.Component<Props> {
|
||||
public state: State = {
|
||||
labelInputTouched: false
|
||||
};
|
||||
|
||||
private labelInput: HTMLInputElement | null = null;
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
this.setState({ label: nextProps.label, mostRecentValidLabel: nextProps.label });
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
address,
|
||||
temporaryLabel,
|
||||
labelError,
|
||||
isEditing,
|
||||
onEditClick,
|
||||
onRemoveClick
|
||||
} = this.props;
|
||||
const { labelInputTouched } = this.state;
|
||||
const trOnClick = isEditing ? noop : onEditClick;
|
||||
const hashName = `${address}-hash`;
|
||||
const labelName = `${address}-label`;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="AddressBookTable-row" onClick={trOnClick}>
|
||||
<div className="AddressBookTable-row-input">
|
||||
<div className="AddressBookTable-row-input-wrapper">
|
||||
<label htmlFor={hashName} className="AddressBookTable-row-input-wrapper-label">
|
||||
{translate('ADDRESS')}
|
||||
</label>
|
||||
<Input
|
||||
name={hashName}
|
||||
title={address}
|
||||
value={address}
|
||||
readOnly={true}
|
||||
isValid={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="AddressBookTable-row-identicon AddressBookTable-row-identicon-non-mobile">
|
||||
<Identicon address={address} />
|
||||
</div>
|
||||
<div className="AddressBookTable-row-identicon AddressBookTable-row-identicon-mobile">
|
||||
<Identicon address={address} size="3rem" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="AddressBookTable-row-input">
|
||||
<div className="AddressBookTable-row-input-wrapper">
|
||||
<label htmlFor={labelName} className="AddressBookTable-row-input-wrapper-label">
|
||||
{translate('LABEL')}
|
||||
</label>
|
||||
<Input
|
||||
name={labelName}
|
||||
title={`${translateRaw('EDIT_LABEL_FOR')} ${address}`}
|
||||
value={temporaryLabel}
|
||||
onChange={this.handleLabelChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onFocus={this.setLabelTouched}
|
||||
onBlur={this.handleBlur}
|
||||
showInvalidBeforeBlur={true}
|
||||
setInnerRef={this.setLabelInputRef}
|
||||
isValid={!(labelInputTouched && labelError)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
title={translateRaw('REMOVE_LABEL')}
|
||||
className="btn btn-sm btn-danger"
|
||||
onClick={onRemoveClick}
|
||||
>
|
||||
<i className="fa fa-close" />
|
||||
</button>
|
||||
</div>
|
||||
{labelError && (
|
||||
<div className="AddressBookTable-row AddressBookTable-row-error AddressBookTable-row-error--mobile">
|
||||
<label className="AddressBookTable-row-input-wrapper-error">{labelError}</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{labelError && (
|
||||
<div className="AddressBookTable-row AddressBookTable-row-error AddressBookTable-row-error--non-mobile">
|
||||
<label className="AddressBookTable-row-input-wrapper-error">{labelError}</label>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const { labelInputTouched } = this.state;
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.key === 'Enter' && this.labelInput) {
|
||||
this.labelInput.blur();
|
||||
} else if (!labelInputTouched) {
|
||||
this.setLabelTouched();
|
||||
}
|
||||
};
|
||||
|
||||
private handleBlur = () => {
|
||||
this.clearLabelTouched();
|
||||
this.props.onSave();
|
||||
this.props.onLabelInputBlur();
|
||||
};
|
||||
|
||||
private setLabelInputRef = (node: HTMLInputElement) => (this.labelInput = node);
|
||||
|
||||
private setLabelTouched = () =>
|
||||
!this.state.labelInputTouched && this.setState({ labelInputTouched: true });
|
||||
|
||||
private clearLabelTouched = () => this.setState({ labelInputTouched: false });
|
||||
|
||||
private handleLabelChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const label = e.target.value;
|
||||
|
||||
this.props.onChange(label);
|
||||
|
||||
this.setState(
|
||||
{ labelInputTouched: true },
|
||||
() => label.length === 0 && this.clearLabelTouched()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default AddressBookTableRow;
|
|
@ -9,25 +9,35 @@ interface Props {
|
|||
isReadOnly?: boolean;
|
||||
isSelfAddress?: boolean;
|
||||
isCheckSummed?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
}
|
||||
|
||||
export const AddressField: React.SFC<Props> = ({ isReadOnly, isSelfAddress, isCheckSummed }) => (
|
||||
export const AddressField: React.SFC<Props> = ({
|
||||
isReadOnly,
|
||||
isSelfAddress,
|
||||
isCheckSummed,
|
||||
showLabelMatch
|
||||
}) => (
|
||||
<AddressFieldFactory
|
||||
isSelfAddress={isSelfAddress}
|
||||
withProps={({ currentTo, isValid, onChange, readOnly }) => (
|
||||
showLabelMatch={showLabelMatch}
|
||||
withProps={({ currentTo, isValid, isLabelEntry, onChange, onFocus, onBlur, readOnly }) => (
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate(isSelfAddress ? 'X_ADDRESS' : 'SEND_ADDR')}
|
||||
</div>
|
||||
<Input
|
||||
className={`input-group-input ${isValid ? '' : 'invalid'}`}
|
||||
className={`input-group-input ${!isValid && !isLabelEntry ? 'invalid' : ''}`}
|
||||
isValid={isValid}
|
||||
type="text"
|
||||
value={isCheckSummed ? toChecksumAddress(currentTo.raw) : currentTo.raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
spellCheck={false}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
@import 'common/sass/variables';
|
||||
@import 'common/sass/variables/colors';
|
||||
@import 'common/sass/variables/spacing';
|
||||
|
||||
.AddressFieldDropdown {
|
||||
position: absolute;
|
||||
top: calc(100% - #{$space});
|
||||
width: 100%;
|
||||
border: 1px solid $gray-lighter;
|
||||
border-top: none;
|
||||
background: $component-active-color;
|
||||
z-index: 1;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-shadow: $tab-box-shadow;
|
||||
text-align: center;
|
||||
|
||||
&-dropdown-item {
|
||||
margin: 0;
|
||||
padding: $space-md $space;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: $gray-lightest;
|
||||
}
|
||||
|
||||
&-identicon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: $space-sm;
|
||||
|
||||
.Identicon {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
@media (max-width: 380px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-label,
|
||||
&-address {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&-label {
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
&-address {
|
||||
flex: 3 0 0;
|
||||
}
|
||||
|
||||
&-no-match {
|
||||
word-break: break-word;
|
||||
|
||||
&:hover {
|
||||
cursor: not-allowed;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: $space-sm;
|
||||
}
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $gray-lighter;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { setCurrentTo, TSetCurrentTo } from 'actions/transaction';
|
||||
import { getLabelAddresses } from 'selectors/addressBook';
|
||||
import { getToRaw } from 'selectors/transaction/fields';
|
||||
import { Address, Identicon } from 'components/ui';
|
||||
import './AddressFieldDropdown.scss';
|
||||
|
||||
interface StateProps {
|
||||
labelAddresses: ReturnType<typeof getLabelAddresses>;
|
||||
currentTo: ReturnType<typeof getToRaw>;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setCurrentTo: TSetCurrentTo;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps;
|
||||
|
||||
interface State {
|
||||
activeIndex: number | null;
|
||||
}
|
||||
|
||||
class AddressFieldDropdown extends React.Component<Props> {
|
||||
public state: State = {
|
||||
activeIndex: null
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
window.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { currentTo } = this.props;
|
||||
const noMatchContent = currentTo.startsWith('0x') ? null : (
|
||||
<li className="AddressFieldDropdown-dropdown-item AddressFieldDropdown-dropdown-item-no-match">
|
||||
<i className="fa fa-warning" /> {translate('NO_LABEL_FOUND_CONTAINING')} "{currentTo}".
|
||||
</li>
|
||||
);
|
||||
|
||||
return this.props.currentTo.length > 1 ? (
|
||||
<ul className="AddressFieldDropdown" role="listbox">
|
||||
{this.getFilteredLabels().length > 0 ? this.renderDropdownItems() : noMatchContent}
|
||||
</ul>
|
||||
) : null;
|
||||
}
|
||||
|
||||
private renderDropdownItems = () =>
|
||||
this.getFilteredLabels().map((filteredLabel, index: number) => {
|
||||
const { activeIndex } = this.state;
|
||||
const { address, label } = filteredLabel;
|
||||
const isActive = activeIndex === index;
|
||||
const className = `AddressFieldDropdown-dropdown-item ${
|
||||
isActive ? 'AddressFieldDropdown-dropdown-item--active' : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={address}
|
||||
className={className}
|
||||
onClick={() => this.props.setCurrentTo(address)}
|
||||
role="option"
|
||||
title={`${translateRaw('SEND_TO')}${label}`}
|
||||
>
|
||||
<div className="AddressFieldDropdown-dropdown-item-identicon">
|
||||
<Identicon address={address} />
|
||||
</div>
|
||||
<strong className="AddressFieldDropdown-dropdown-item-label">{label}</strong>
|
||||
<em className="AddressFieldDropdown-dropdown-item-address">
|
||||
<Address address={address} />
|
||||
</em>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
private getFilteredLabels = () =>
|
||||
Object.keys(this.props.labelAddresses)
|
||||
.filter(label => label.toLowerCase().includes(this.props.currentTo.toLowerCase()))
|
||||
.map(label => ({ address: this.props.labelAddresses[label], label }))
|
||||
.slice(0, 5);
|
||||
|
||||
private getIsVisible = () =>
|
||||
this.props.currentTo.length > 1 && this.getFilteredLabels().length > 0;
|
||||
|
||||
private handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (this.getIsVisible()) {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
return this.handleEnterKeyDown();
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
return this.handleUpArrowKeyDown();
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
return this.handleDownArrowKeyDown();
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleEnterKeyDown = () => {
|
||||
const { activeIndex } = this.state;
|
||||
|
||||
if (activeIndex !== null) {
|
||||
const filteredLabels = this.getFilteredLabels();
|
||||
|
||||
filteredLabels.forEach(({ address }, index) => {
|
||||
if (activeIndex === index) {
|
||||
this.props.setCurrentTo(address);
|
||||
}
|
||||
});
|
||||
|
||||
this.clearActiveIndex();
|
||||
}
|
||||
};
|
||||
|
||||
private handleUpArrowKeyDown = () => {
|
||||
const { activeIndex: previousActiveIndex } = this.state;
|
||||
const filteredLabelCount = this.getFilteredLabels().length;
|
||||
|
||||
let activeIndex =
|
||||
previousActiveIndex === null ? filteredLabelCount - 1 : previousActiveIndex - 1;
|
||||
|
||||
// Loop back to end
|
||||
if (activeIndex < 0) {
|
||||
activeIndex = filteredLabelCount - 1;
|
||||
}
|
||||
|
||||
this.setState({ activeIndex });
|
||||
};
|
||||
|
||||
private handleDownArrowKeyDown = () => {
|
||||
const { activeIndex: previousActiveIndex } = this.state;
|
||||
const filteredLabelCount = this.getFilteredLabels().length;
|
||||
|
||||
let activeIndex = previousActiveIndex === null ? 0 : previousActiveIndex + 1;
|
||||
|
||||
// Loop back to beginning
|
||||
if (activeIndex >= filteredLabelCount) {
|
||||
activeIndex = 0;
|
||||
}
|
||||
|
||||
this.setState({ activeIndex });
|
||||
};
|
||||
|
||||
private setActiveIndex = (activeIndex: number | null) => this.setState({ activeIndex });
|
||||
|
||||
private clearActiveIndex = () => this.setActiveIndex(null);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: AppState) => ({
|
||||
labelAddresses: getLabelAddresses(state),
|
||||
currentTo: getToRaw(state)
|
||||
}),
|
||||
{ setCurrentTo }
|
||||
)(AddressFieldDropdown);
|
|
@ -0,0 +1,3 @@
|
|||
.AddressField {
|
||||
position: relative;
|
||||
}
|
|
@ -4,6 +4,7 @@ import { AddressInputFactory } from './AddressInputFactory';
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ICurrentTo } from 'selectors/transaction';
|
||||
import './AddressFieldFactory.scss';
|
||||
|
||||
interface DispatchProps {
|
||||
setCurrentTo: TSetCurrentTo;
|
||||
|
@ -12,19 +13,33 @@ interface DispatchProps {
|
|||
interface OwnProps {
|
||||
to: string | null;
|
||||
isSelfAddress?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isFocused: boolean;
|
||||
}
|
||||
|
||||
export interface CallbackProps {
|
||||
isValid: boolean;
|
||||
isLabelEntry: boolean;
|
||||
readOnly: boolean;
|
||||
currentTo: ICurrentTo;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
onFocus(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
onBlur(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & OwnProps;
|
||||
|
||||
class AddressFieldFactoryClass extends React.Component<Props> {
|
||||
public state: State = {
|
||||
isFocused: false
|
||||
};
|
||||
|
||||
private goingToBlur: number | null = null;
|
||||
|
||||
public componentDidMount() {
|
||||
// this 'to' parameter can be either token or actual field related
|
||||
const { to } = this.props;
|
||||
|
@ -33,34 +48,62 @@ class AddressFieldFactoryClass extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
if (this.goingToBlur) {
|
||||
window.clearTimeout(this.goingToBlur);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<AddressInputFactory
|
||||
isSelfAddress={this.props.isSelfAddress}
|
||||
onChange={this.setAddress}
|
||||
withProps={this.props.withProps}
|
||||
/>
|
||||
<div className="AddressField">
|
||||
<AddressInputFactory
|
||||
isSelfAddress={this.props.isSelfAddress}
|
||||
showLabelMatch={this.props.showLabelMatch}
|
||||
isFocused={this.state.isFocused}
|
||||
onChange={this.setAddress}
|
||||
onFocus={this.focus}
|
||||
onBlur={this.setBlurTimeout}
|
||||
withProps={this.props.withProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private focus = () => this.setState({ isFocused: true });
|
||||
|
||||
private blur = () => this.setState({ isFocused: false });
|
||||
|
||||
private setAddress = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { value } = ev.currentTarget;
|
||||
this.props.setCurrentTo(value);
|
||||
};
|
||||
|
||||
private setBlurTimeout = () => (this.goingToBlur = window.setTimeout(this.blur, 150));
|
||||
}
|
||||
|
||||
const AddressFieldFactory = connect(null, { setCurrentTo })(AddressFieldFactoryClass);
|
||||
|
||||
interface DefaultAddressFieldProps {
|
||||
isSelfAddress?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
const DefaultAddressField: React.SFC<DefaultAddressFieldProps> = ({ isSelfAddress, withProps }) => (
|
||||
const DefaultAddressField: React.SFC<DefaultAddressFieldProps> = ({
|
||||
isSelfAddress,
|
||||
showLabelMatch,
|
||||
withProps
|
||||
}) => (
|
||||
<Query
|
||||
params={['to']}
|
||||
withQuery={({ to }) => (
|
||||
<AddressFieldFactory to={to} isSelfAddress={isSelfAddress} withProps={withProps} />
|
||||
<AddressFieldFactory
|
||||
to={to}
|
||||
isSelfAddress={isSelfAddress}
|
||||
showLabelMatch={showLabelMatch}
|
||||
withProps={withProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
@import 'common/sass/variables';
|
||||
@import 'common/sass/variables/spacing';
|
||||
@import 'common/sass/variables/colors';
|
||||
|
||||
.AddressInput {
|
||||
display: flex;
|
||||
|
@ -6,11 +8,25 @@
|
|||
flex-wrap: nowrap;
|
||||
|
||||
&-input {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
|
||||
&-with-label {
|
||||
margin-bottom: $space-lg;
|
||||
}
|
||||
|
||||
&-label {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
color: darken($brand-success, 15%);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
&-identicon {
|
||||
padding-top: .75rem;
|
||||
padding-top: $space-md;
|
||||
transform: translateX(20%);
|
||||
|
||||
@media (max-width: $screen-sm) {
|
||||
|
@ -21,5 +37,9 @@
|
|||
height: 3.4rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 380px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Identicon, Spinner } from 'components/ui';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { ICurrentTo, getCurrentTo, isValidCurrentTo } from 'selectors/transaction';
|
||||
import { translateRaw } from 'translations';
|
||||
import {
|
||||
ICurrentTo,
|
||||
getCurrentTo,
|
||||
isValidCurrentTo,
|
||||
isCurrentToLabelEntry
|
||||
} from 'selectors/transaction';
|
||||
import { getCurrentToLabel } from 'selectors/addressBook';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { CallbackProps } from 'components/AddressFieldFactory';
|
||||
|
@ -10,17 +17,24 @@ import { getWalletInst } from 'selectors/wallet';
|
|||
import { getResolvingDomain } from 'selectors/ens';
|
||||
import { isValidENSAddress } from 'libs/validators';
|
||||
import { Address } from 'libs/units';
|
||||
import AddressFieldDropdown from './AddressFieldDropdown';
|
||||
import './AddressInputFactory.scss';
|
||||
|
||||
interface StateProps {
|
||||
currentTo: ICurrentTo;
|
||||
label: string | null;
|
||||
isValid: boolean;
|
||||
isLabelEntry: boolean;
|
||||
isResolving: boolean;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
isSelfAddress?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
isFocused?: boolean;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
onFocus(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
onBlur(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
|
@ -46,24 +60,51 @@ type Props = OwnProps & StateProps;
|
|||
|
||||
class AddressInputFactoryClass extends Component<Props> {
|
||||
public render() {
|
||||
const { currentTo, onChange, isValid, withProps, isSelfAddress, isResolving } = this.props;
|
||||
const {
|
||||
label,
|
||||
currentTo,
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
isValid,
|
||||
isLabelEntry,
|
||||
withProps,
|
||||
showLabelMatch,
|
||||
isSelfAddress,
|
||||
isResolving,
|
||||
isFocused
|
||||
} = this.props;
|
||||
const { value } = currentTo;
|
||||
const addr = addHexPrefix(value ? value.toString('hex') : '0');
|
||||
const inputClassName = `AddressInput-input ${label ? 'AddressInput-input-with-label' : ''}`;
|
||||
const sendingTo = `${translateRaw('SENDING_TO')} ${label}`;
|
||||
const isENSAddress = currentTo.raw.includes('.eth');
|
||||
|
||||
return (
|
||||
<div className="AddressInput form-group">
|
||||
<div className="AddressInput-input">
|
||||
<div className={inputClassName}>
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) =>
|
||||
withProps({
|
||||
currentTo,
|
||||
isValid,
|
||||
isLabelEntry,
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
readOnly: !!(readOnly || this.props.isResolving || isSelfAddress)
|
||||
})
|
||||
}
|
||||
/>
|
||||
<ENSStatus ensAddress={currentTo.raw} isLoading={isResolving} rawAddress={addr} />
|
||||
{isFocused && !isENSAddress && <AddressFieldDropdown />}
|
||||
{showLabelMatch &&
|
||||
label && (
|
||||
<div title={sendingTo} className="AddressInput-input-label">
|
||||
<i className="fa fa-check" /> {sendingTo}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="AddressInput-identicon">
|
||||
<Identicon address={addr} />
|
||||
|
@ -88,7 +129,9 @@ export const AddressInputFactory = connect((state: AppState, ownProps: OwnProps)
|
|||
|
||||
return {
|
||||
currentTo,
|
||||
label: getCurrentToLabel(state),
|
||||
isResolving: getResolvingDomain(state),
|
||||
isValid: isValidCurrentTo(state)
|
||||
isValid: isValidCurrentTo(state),
|
||||
isLabelEntry: isCurrentToLabelEntry(state)
|
||||
};
|
||||
})(AddressInputFactoryClass);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
import React from 'react';
|
||||
import { connect, MapStateToProps } from 'react-redux';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { AppState } from 'reducers';
|
||||
import {
|
||||
changeAddressLabelEntry,
|
||||
TChangeAddressLabelEntry,
|
||||
saveAddressLabelEntry,
|
||||
TSaveAddressLabelEntry,
|
||||
removeAddressLabelEntry,
|
||||
TRemoveAddressLabelEntry
|
||||
} from 'actions/addressBook';
|
||||
import { getAccountAddressEntry, getAddressLabels } from 'selectors/addressBook';
|
||||
import { Address, Identicon, Input } from 'components/ui';
|
||||
|
||||
interface StateProps {
|
||||
entry: ReturnType<typeof getAccountAddressEntry>;
|
||||
addressLabels: ReturnType<typeof getAddressLabels>;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
changeAddressLabelEntry: TChangeAddressLabelEntry;
|
||||
saveAddressLabelEntry: TSaveAddressLabelEntry;
|
||||
removeAddressLabelEntry: TRemoveAddressLabelEntry;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
address: string;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps & OwnProps;
|
||||
|
||||
interface State {
|
||||
copied: boolean;
|
||||
editingLabel: boolean;
|
||||
labelInputTouched: boolean;
|
||||
}
|
||||
|
||||
export const ACCOUNT_ADDRESS_ID: string = 'ACCOUNT_ADDRESS_ID';
|
||||
|
||||
class AccountAddress extends React.Component<Props, State> {
|
||||
public state = {
|
||||
copied: false,
|
||||
editingLabel: false,
|
||||
labelInputTouched: false
|
||||
};
|
||||
|
||||
private goingToClearCopied: number | null = null;
|
||||
|
||||
private labelInput: HTMLInputElement | null = null;
|
||||
|
||||
public handleCopy = () =>
|
||||
this.setState(
|
||||
(prevState: State) => ({
|
||||
copied: !prevState.copied
|
||||
}),
|
||||
this.clearCopied
|
||||
);
|
||||
|
||||
public componentWillUnmount() {
|
||||
if (this.goingToClearCopied) {
|
||||
window.clearTimeout(this.goingToClearCopied);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { address, addressLabels } = this.props;
|
||||
const { copied } = this.state;
|
||||
const label = addressLabels[address];
|
||||
const labelContent = this.generateLabelContent();
|
||||
const labelButton = this.generateLabelButton();
|
||||
const addressClassName = `AccountInfo-address-addr ${
|
||||
label ? 'AccountInfo-address-addr--small' : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className="AccountInfo">
|
||||
<h5 className="AccountInfo-section-header">{translate('SIDEBAR_ACCOUNTADDR')}</h5>
|
||||
<div className="AccountInfo-section AccountInfo-address-section">
|
||||
<div className="AccountInfo-address-icon">
|
||||
<Identicon address={address} size="100%" />
|
||||
</div>
|
||||
<div className="AccountInfo-address-wrapper">
|
||||
{labelContent}
|
||||
<div className={addressClassName}>
|
||||
<Address address={address} />
|
||||
</div>
|
||||
<CopyToClipboard onCopy={this.handleCopy} text={address}>
|
||||
<div
|
||||
className={`AccountInfo-copy ${copied ? 'is-copied' : ''}`}
|
||||
title="Copy To clipboard"
|
||||
>
|
||||
<i className="fa fa-copy" />
|
||||
<span>{copied ? 'copied!' : 'copy address'}</span>
|
||||
</div>
|
||||
</CopyToClipboard>
|
||||
<div className="AccountInfo-label" title="Edit label">
|
||||
{labelButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private clearCopied = () =>
|
||||
(this.goingToClearCopied = window.setTimeout(() => this.setState({ copied: false }), 2000));
|
||||
|
||||
private startEditingLabel = () =>
|
||||
this.setState({ editingLabel: true }, () => {
|
||||
if (this.labelInput) {
|
||||
this.labelInput.focus();
|
||||
this.labelInput.select();
|
||||
}
|
||||
});
|
||||
|
||||
private stopEditingLabel = () => this.setState({ editingLabel: false });
|
||||
|
||||
private setLabelInputRef = (node: HTMLInputElement) => (this.labelInput = node);
|
||||
|
||||
private generateLabelContent = () => {
|
||||
const { address, addressLabels, entry: { temporaryLabel, labelError } } = this.props;
|
||||
const { editingLabel, labelInputTouched } = this.state;
|
||||
const storedLabel = addressLabels[address];
|
||||
const newLabelSameAsPrevious = temporaryLabel === storedLabel;
|
||||
const labelInputTouchedWithError = labelInputTouched && !newLabelSameAsPrevious && labelError;
|
||||
|
||||
let labelContent = null;
|
||||
|
||||
if (editingLabel) {
|
||||
labelContent = (
|
||||
<React.Fragment>
|
||||
<Input
|
||||
title={translateRaw('ADD_LABEL')}
|
||||
placeholder={translateRaw('NEW_LABEL')}
|
||||
defaultValue={storedLabel}
|
||||
onChange={this.handleLabelChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onFocus={this.setTemporaryLabelTouched}
|
||||
onBlur={this.handleBlur}
|
||||
showInvalidBeforeBlur={true}
|
||||
setInnerRef={this.setLabelInputRef}
|
||||
isValid={!labelInputTouchedWithError}
|
||||
/>
|
||||
{labelInputTouchedWithError && (
|
||||
<label className="AccountInfo-address-wrapper-error">{labelError}</label>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
labelContent = (
|
||||
<label title={storedLabel} className="AccountInfo-address-label">
|
||||
{storedLabel}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
return labelContent;
|
||||
};
|
||||
|
||||
private generateLabelButton = () => {
|
||||
const { address, addressLabels } = this.props;
|
||||
const { editingLabel } = this.state;
|
||||
const label = addressLabels[address];
|
||||
const labelButton = editingLabel ? (
|
||||
<React.Fragment>
|
||||
<i className="fa fa-save" />
|
||||
<span role="button" title={translateRaw('SAVE_LABEL')} onClick={this.stopEditingLabel}>
|
||||
{translate('SAVE_LABEL')}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<i className="fa fa-pencil" />
|
||||
<span
|
||||
role="button"
|
||||
title={label ? translateRaw('EDIT_LABEL') : translateRaw('ADD_LABEL_9')}
|
||||
onClick={this.startEditingLabel}
|
||||
>
|
||||
{label ? translate('EDIT_LABEL') : translate('ADD_LABEL_9')}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return labelButton;
|
||||
};
|
||||
|
||||
private handleBlur = () => {
|
||||
const { address, addressLabels, entry: { id, label, temporaryLabel, labelError } } = this.props;
|
||||
const storedLabel = addressLabels[address];
|
||||
|
||||
this.clearTemporaryLabelTouched();
|
||||
this.stopEditingLabel();
|
||||
|
||||
if (temporaryLabel === storedLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (temporaryLabel && temporaryLabel.length > 0) {
|
||||
this.props.saveAddressLabelEntry(id);
|
||||
|
||||
if (labelError) {
|
||||
// If the new changes aren't valid, undo them.
|
||||
this.props.changeAddressLabelEntry({
|
||||
id,
|
||||
address,
|
||||
temporaryAddress: address,
|
||||
label,
|
||||
temporaryLabel: label,
|
||||
overrideValidation: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.props.removeAddressLabelEntry(id);
|
||||
}
|
||||
};
|
||||
|
||||
private handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
return this.handleBlur();
|
||||
case 'Escape':
|
||||
return this.stopEditingLabel();
|
||||
}
|
||||
};
|
||||
|
||||
private handleLabelChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { address } = this.props;
|
||||
const label = e.target.value;
|
||||
|
||||
this.props.changeAddressLabelEntry({
|
||||
id: ACCOUNT_ADDRESS_ID,
|
||||
address,
|
||||
label,
|
||||
isEditing: true
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
labelInputTouched: true
|
||||
},
|
||||
() => label.length === 0 && this.clearTemporaryLabelTouched()
|
||||
);
|
||||
};
|
||||
|
||||
private setTemporaryLabelTouched = () => {
|
||||
const { labelInputTouched } = this.state;
|
||||
|
||||
if (!labelInputTouched) {
|
||||
this.setState({ labelInputTouched: true });
|
||||
}
|
||||
};
|
||||
|
||||
private clearTemporaryLabelTouched = () => this.setState({ labelInputTouched: false });
|
||||
}
|
||||
|
||||
const mapStateToProps: MapStateToProps<StateProps, {}, AppState> = (state: AppState) => ({
|
||||
entry: getAccountAddressEntry(state),
|
||||
addressLabels: getAddressLabels(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps: DispatchProps = {
|
||||
changeAddressLabelEntry,
|
||||
saveAddressLabelEntry,
|
||||
removeAddressLabelEntry
|
||||
};
|
||||
|
||||
export default connect<StateProps, DispatchProps, OwnProps, AppState>(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AccountAddress);
|
|
@ -10,11 +10,11 @@
|
|||
opacity: 0.5;
|
||||
transition: $transition;
|
||||
|
||||
&:hover{
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.is-copied{
|
||||
&.is-copied {
|
||||
color: $brand-success;
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -24,8 +24,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-label {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
color: $text-color;
|
||||
font-size: $font-size-xs;
|
||||
opacity: 0.5;
|
||||
|
||||
.fa {
|
||||
margin: 0 $space-xs 0 $space-sm;
|
||||
}
|
||||
}
|
||||
|
||||
&-section {
|
||||
margin-top: $space * 1.5;
|
||||
margin: $space 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
|
@ -72,10 +84,16 @@
|
|||
max-width: 100%;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
max-width: calc(100% - 44px - 24px);
|
||||
|
||||
&-error {
|
||||
color: $brand-danger;
|
||||
margin-bottom: $space-md;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
|
@ -88,6 +106,10 @@
|
|||
margin-top: -$space-xs;
|
||||
word-wrap: break-word;
|
||||
@include mono;
|
||||
|
||||
&--small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-confirm {
|
||||
|
@ -97,6 +119,14 @@
|
|||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&-label {
|
||||
font-weight: bolder;
|
||||
font-size: 1.3rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
&-balance {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { Identicon, UnitDisplay, Address, NewTabLink } from 'components/ui';
|
||||
import { IWallet, Balance, TrezorWallet, LedgerWallet } from 'libs/wallet';
|
||||
import { UnitDisplay, NewTabLink } from 'components/ui';
|
||||
import { IWallet, TrezorWallet, LedgerWallet, Balance } from 'libs/wallet';
|
||||
import translate from 'translations';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||
|
@ -12,6 +11,7 @@ import { NetworkConfig } from 'types/network';
|
|||
import { TRefreshAccountBalance, refreshAccountBalance } from 'actions/wallet';
|
||||
import { etherChainExplorerInst } from 'config/data';
|
||||
import './AccountInfo.scss';
|
||||
import AccountAddress from './AccountAddress';
|
||||
|
||||
interface OwnProps {
|
||||
wallet: IWallet;
|
||||
|
@ -19,15 +19,14 @@ interface OwnProps {
|
|||
|
||||
interface StateProps {
|
||||
balance: Balance;
|
||||
network: NetworkConfig;
|
||||
isOffline: boolean;
|
||||
network: ReturnType<typeof getNetworkConfig>;
|
||||
isOffline: ReturnType<typeof getOffline>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showLongBalance: boolean;
|
||||
address: string;
|
||||
confirmAddr: boolean;
|
||||
copied: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -40,8 +39,7 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
public state = {
|
||||
showLongBalance: false,
|
||||
address: '',
|
||||
confirmAddr: false,
|
||||
copied: false
|
||||
confirmAddr: false
|
||||
};
|
||||
|
||||
public setAddressFromWallet() {
|
||||
|
@ -74,20 +72,10 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
});
|
||||
};
|
||||
|
||||
public onCopy = () => {
|
||||
this.setState(state => {
|
||||
return {
|
||||
copied: !state.copied
|
||||
};
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.setState({ copied: false });
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { network, balance, isOffline } = this.props;
|
||||
const { network, isOffline, balance } = this.props;
|
||||
const { address, showLongBalance, confirmAddr } = this.state;
|
||||
|
||||
let blockExplorer;
|
||||
let tokenExplorer;
|
||||
if (!network.isCustom) {
|
||||
|
@ -98,27 +86,8 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
|
||||
const wallet = this.props.wallet as LedgerWallet | TrezorWallet;
|
||||
return (
|
||||
<div className="AccountInfo">
|
||||
<h5 className="AccountInfo-section-header">{translate('SIDEBAR_ACCOUNTADDR')}</h5>
|
||||
<div className="AccountInfo-section AccountInfo-address-section">
|
||||
<div className="AccountInfo-address-icon">
|
||||
<Identicon address={address} size="100%" />
|
||||
</div>
|
||||
<div className="AccountInfo-address-wrapper">
|
||||
<div className="AccountInfo-address-addr">
|
||||
<Address address={address} />
|
||||
</div>
|
||||
<CopyToClipboard onCopy={this.onCopy} text={toChecksumAddress(address)}>
|
||||
<div
|
||||
className={`AccountInfo-copy ${this.state.copied ? 'is-copied' : ''}`}
|
||||
title="Copy To clipboard"
|
||||
>
|
||||
<i className="fa fa-copy" />
|
||||
<span>{this.state.copied ? 'copied!' : 'copy address'}</span>
|
||||
</div>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<AccountAddress address={toChecksumAddress(address)} />
|
||||
|
||||
{typeof wallet.displayAddress === 'function' && (
|
||||
<div className="AccountInfo-section">
|
||||
|
@ -160,7 +129,7 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
unit={'ether'}
|
||||
displayShortBalance={!showLongBalance}
|
||||
checkOffline={true}
|
||||
symbol={balance.wei ? network.name : null}
|
||||
symbol={balance.wei ? this.setSymbol(network) : null}
|
||||
/>
|
||||
</span>
|
||||
{balance.wei && (
|
||||
|
@ -214,7 +183,15 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private setSymbol(network: NetworkConfig) {
|
||||
if (network.isTestnet) {
|
||||
return network.unit + ' (' + network.name + ')';
|
||||
}
|
||||
return network.unit;
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return {
|
||||
balance: state.wallet.balance,
|
||||
|
|
|
@ -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 ||
|
||||
|
|
|
@ -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;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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)`}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
};
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 })}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 || '' });
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
.OnlineStatus {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
text-align: center;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>) => {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,10 @@ $speed: 500ms;
|
|||
text-align: center;
|
||||
padding-bottom: $space;
|
||||
|
||||
@media (max-width: $screen-md) {
|
||||
padding-bottom: $space * 2;
|
||||
}
|
||||
|
||||
&-back {
|
||||
@include reset-button;
|
||||
position: absolute;
|
||||
|
|
|
@ -166,7 +166,8 @@ const WalletDecrypt = withRouter<Props>(
|
|||
component: TrezorDecrypt,
|
||||
initialParams: {},
|
||||
unlock: this.props.setWallet,
|
||||
helpLink: 'https://doc.satoshilabs.com/trezor-apps/mew.html'
|
||||
helpLink:
|
||||
'https://support.mycrypto.com/accessing-your-wallet/how-to-use-your-trezor-with-mycrypto.html'
|
||||
},
|
||||
[SecureWalletName.PARITY_SIGNER]: {
|
||||
lid: 'X_PARITYSIGNER',
|
||||
|
@ -225,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({
|
||||
|
|
|
@ -37,16 +37,10 @@
|
|||
padding-right: $space;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&-addresses {
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
&-table {
|
||||
width: 732px;
|
||||
text-align: center;
|
||||
|
@ -61,9 +55,19 @@
|
|||
font-size: 13px;
|
||||
text-align: left;
|
||||
font-family: $font-family-monospace;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 6px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,9 +78,20 @@
|
|||
background-image: url('~assets/images/icon-external-link.svg');
|
||||
}
|
||||
|
||||
&-na {
|
||||
font-size: $font-size-xs;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
// Specific selectors to override bootstrap
|
||||
tbody {
|
||||
tr {
|
||||
cursor: pointer;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-family: $font-family-sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Select, { Option } from 'react-select';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import {
|
||||
DeterministicWalletData,
|
||||
|
@ -14,39 +15,42 @@ import Modal, { IButton } from 'components/ui/Modal';
|
|||
import { AppState } from 'reducers';
|
||||
import { isValidPath } from 'libs/validators';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { getTokens, MergedToken } from 'selectors/wallet';
|
||||
import { getTokens } from 'selectors/wallet';
|
||||
import { getAddressLabels } from 'selectors/addressBook';
|
||||
import { UnitDisplay, Input } from 'components/ui';
|
||||
import { StaticNetworkConfig } from 'types/network';
|
||||
import './DeterministicWalletsModal.scss';
|
||||
|
||||
const WALLETS_PER_PAGE = 5;
|
||||
|
||||
interface Props {
|
||||
// Passed props
|
||||
interface OwnProps {
|
||||
isOpen?: boolean;
|
||||
dPath: string;
|
||||
dPath: DPath;
|
||||
dPaths: DPath[];
|
||||
publicKey?: string;
|
||||
chainCode?: string;
|
||||
seed?: string;
|
||||
|
||||
// Redux state
|
||||
wallets: AppState['deterministicWallets']['wallets'];
|
||||
desiredToken: AppState['deterministicWallets']['desiredToken'];
|
||||
network: StaticNetworkConfig;
|
||||
tokens: MergedToken[];
|
||||
|
||||
// Redux actions
|
||||
getDeterministicWallets(args: GetDeterministicWalletsArgs): GetDeterministicWalletsAction;
|
||||
setDesiredToken(tkn: string | undefined): SetDesiredTokenAction;
|
||||
|
||||
onCancel(): void;
|
||||
onConfirmAddress(address: string, addressIndex: number): void;
|
||||
onPathChange(path: string): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
addressLabels: ReturnType<typeof getAddressLabels>;
|
||||
wallets: AppState['deterministicWallets']['wallets'];
|
||||
desiredToken: AppState['deterministicWallets']['desiredToken'];
|
||||
network: ReturnType<typeof getNetworkConfig>;
|
||||
tokens: ReturnType<typeof getTokens>;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getDeterministicWallets(args: GetDeterministicWalletsArgs): GetDeterministicWalletsAction;
|
||||
setDesiredToken(tkn: string | undefined): SetDesiredTokenAction;
|
||||
onCancel(): void;
|
||||
onConfirmAddress(address: string, addressIndex: number): void;
|
||||
onPathChange(dPath: DPath): void;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
interface State {
|
||||
currentLabel: string;
|
||||
currentDPath: DPath;
|
||||
selectedAddress: string;
|
||||
selectedAddrIndex: number;
|
||||
isCustomPath: boolean;
|
||||
|
@ -65,7 +69,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
selectedAddrIndex: 0,
|
||||
isCustomPath: false,
|
||||
customPath: '',
|
||||
currentLabel: '',
|
||||
currentDPath: this.props.dPath,
|
||||
page: 0
|
||||
};
|
||||
|
||||
|
@ -73,7 +77,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 ||
|
||||
|
@ -86,7 +90,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { wallets, desiredToken, network, tokens, dPath, dPaths, onCancel } = this.props;
|
||||
const { wallets, desiredToken, network, tokens, dPaths, onCancel } = this.props;
|
||||
const { selectedAddress, customPath, page } = this.state;
|
||||
|
||||
const buttons: IButton[] = [
|
||||
|
@ -119,7 +123,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
<div className="DWModal-path-select">
|
||||
<Select
|
||||
name="fieldDPath"
|
||||
value={this.state.currentLabel || this.findDPath('value', dPath).value}
|
||||
value={this.state.currentDPath}
|
||||
onChange={this.handleChangePath}
|
||||
options={dPaths.concat([customDPath])}
|
||||
optionRenderer={this.renderDPathOption}
|
||||
|
@ -128,11 +132,11 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
searchable={false}
|
||||
/>
|
||||
</div>
|
||||
{this.state.currentLabel === customDPath.label && (
|
||||
{this.state.currentDPath.label === customDPath.label && (
|
||||
<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}
|
||||
|
@ -199,12 +203,12 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
const { dPath, publicKey, chainCode, seed } = props;
|
||||
|
||||
if (dPath && ((publicKey && chainCode) || seed)) {
|
||||
if (isValidPath(dPath)) {
|
||||
if (isValidPath(dPath.value)) {
|
||||
this.props.getDeterministicWallets({
|
||||
seed,
|
||||
dPath,
|
||||
publicKey,
|
||||
chainCode,
|
||||
dPath: dPath.value,
|
||||
limit: WALLETS_PER_PAGE,
|
||||
offset: WALLETS_PER_PAGE * this.state.page
|
||||
});
|
||||
|
@ -214,19 +218,12 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private findDPath = (prop: keyof DPath, cmp: string) => {
|
||||
return this.props.dPaths.find(d => d[prop] === cmp) || customDPath;
|
||||
};
|
||||
|
||||
private handleChangePath = (newPath: DPath) => {
|
||||
const { value: dPathLabel } = newPath;
|
||||
const { value } = this.findDPath('value', dPathLabel);
|
||||
|
||||
if (value === customDPath.value) {
|
||||
this.setState({ isCustomPath: true, currentLabel: dPathLabel });
|
||||
if (newPath.value === customDPath.value) {
|
||||
this.setState({ isCustomPath: true, currentDPath: newPath });
|
||||
} else {
|
||||
this.setState({ isCustomPath: false, currentLabel: dPathLabel });
|
||||
this.props.onPathChange(value);
|
||||
this.setState({ isCustomPath: false, currentDPath: newPath });
|
||||
this.props.onPathChange(newPath);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -235,11 +232,14 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
private handleSubmitCustomPath = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
const { customPath, currentLabel } = this.state;
|
||||
const { customPath, currentDPath } = this.state;
|
||||
ev.preventDefault();
|
||||
|
||||
if (currentLabel === customDPath.label && isValidPath(customPath)) {
|
||||
this.props.onPathChange(customPath);
|
||||
if (currentDPath.value === customDPath.value && isValidPath(customPath)) {
|
||||
this.props.onPathChange({
|
||||
label: customDPath.label,
|
||||
value: customPath
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -278,8 +278,10 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
private renderWalletRow(wallet: DeterministicWalletData) {
|
||||
const { desiredToken, network } = this.props;
|
||||
const { desiredToken, network, addressLabels } = this.props;
|
||||
const { selectedAddress } = this.state;
|
||||
const label = addressLabels[toChecksumAddress(wallet.address)];
|
||||
const spanClassName = label ? 'DWModal-addresses-table-address-text' : '';
|
||||
|
||||
// Get renderable values, but keep 'em short
|
||||
const token = desiredToken ? wallet.tokenValues[desiredToken] : null;
|
||||
|
@ -297,7 +299,10 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
checked={selectedAddress === wallet.address}
|
||||
value={wallet.address}
|
||||
/>
|
||||
{wallet.address}
|
||||
<div>
|
||||
{label && <label className="DWModal-addresses-table-address-label">{label}</label>}
|
||||
<span className={spanClassName}>{wallet.address}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<UnitDisplay
|
||||
|
@ -309,16 +314,16 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
/>
|
||||
</td>
|
||||
<td>
|
||||
{token ? (
|
||||
{desiredToken ? (
|
||||
<UnitDisplay
|
||||
decimal={token.decimal}
|
||||
value={token.value}
|
||||
decimal={token ? token.decimal : 0}
|
||||
value={token ? token.value : null}
|
||||
symbol={desiredToken}
|
||||
displayShortBalance={true}
|
||||
checkOffline={true}
|
||||
/>
|
||||
) : (
|
||||
'???'
|
||||
<span className="DWModal-addresses-table-na">N/A</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
|
@ -335,8 +340,9 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return {
|
||||
addressLabels: getAddressLabels(state),
|
||||
wallets: state.deterministicWallets.wallets,
|
||||
desiredToken: state.deterministicWallets.desiredToken,
|
||||
network: getNetworkConfig(state),
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -25,7 +25,7 @@ interface StateProps {
|
|||
interface State {
|
||||
publicKey: string;
|
||||
chainCode: string;
|
||||
dPath: string;
|
||||
dPath: DPath;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
showTip: boolean;
|
||||
|
@ -37,15 +37,15 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
public state: State = {
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
dPath: this.props.dPath ? this.props.dPath.value : '',
|
||||
dPath: this.props.dPath || this.props.dPaths[0],
|
||||
error: null,
|
||||
isLoading: false,
|
||||
showTip: false
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath ? nextProps.dPath.value : '' });
|
||||
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,18 +116,18 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private handlePathChange = (dPath: string) => {
|
||||
private handlePathChange = (dPath: DPath) => {
|
||||
this.handleConnect(dPath);
|
||||
};
|
||||
|
||||
private handleConnect = (dPath: string = this.state.dPath) => {
|
||||
private handleConnect = (dPath: DPath) => {
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: null,
|
||||
showTip: false
|
||||
});
|
||||
|
||||
LedgerWallet.getChainCode(dPath)
|
||||
LedgerWallet.getChainCode(dPath.value)
|
||||
.then(res => {
|
||||
this.setState({
|
||||
publicKey: res.publicKey,
|
||||
|
@ -149,19 +149,19 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
private handleUnlock = (address: string, index: number) => {
|
||||
this.props.onUnlock(new LedgerWallet(address, this.state.dPath, index));
|
||||
this.props.onUnlock(new LedgerWallet(address, this.state.dPath.value, index));
|
||||
this.reset();
|
||||
};
|
||||
|
||||
private handleNullConnect = (): void => {
|
||||
return this.handleConnect();
|
||||
return this.handleConnect(this.state.dPath);
|
||||
};
|
||||
|
||||
private reset() {
|
||||
this.setState({
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
dPath: this.props.dPath ? this.props.dPath.value : ''
|
||||
dPath: this.props.dPath || this.props.dPaths[0]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ interface State {
|
|||
formattedPhrase: string;
|
||||
pass: string;
|
||||
seed: string;
|
||||
dPath: string;
|
||||
dPath: DPath;
|
||||
}
|
||||
|
||||
class MnemonicDecryptClass extends PureComponent<Props, State> {
|
||||
|
@ -36,12 +36,12 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
|
|||
formattedPhrase: '',
|
||||
pass: '',
|
||||
seed: '',
|
||||
dPath: this.props.dPath.value
|
||||
dPath: this.props.dPath
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath.value });
|
||||
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')}
|
||||
|
@ -131,7 +133,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
|
|||
this.setState({ seed: '' });
|
||||
};
|
||||
|
||||
private handlePathChange = (dPath: string) => {
|
||||
private handlePathChange = (dPath: DPath) => {
|
||||
this.setState({ dPath });
|
||||
};
|
||||
|
||||
|
@ -139,7 +141,7 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
|
|||
const { formattedPhrase, pass, dPath } = this.state;
|
||||
|
||||
this.props.onUnlock({
|
||||
path: `${dPath}/${index}`,
|
||||
path: `${dPath.value}/${index}`,
|
||||
pass,
|
||||
phrase: formattedPhrase,
|
||||
address
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -24,7 +24,7 @@ interface StateProps {
|
|||
interface State {
|
||||
publicKey: string;
|
||||
chainCode: string;
|
||||
dPath: string;
|
||||
dPath: DPath;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
@ -35,14 +35,14 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
|
|||
public state: State = {
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
dPath: this.props.dPath ? this.props.dPath.value : '',
|
||||
dPath: this.props.dPath || this.props.dPaths[0],
|
||||
error: null,
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath ? nextProps.dPath.value : '' });
|
||||
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
|
|||
<div className={`TrezorDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
|
||||
|
||||
<div className="TrezorDecrypt-help">
|
||||
<NewTabLink href="https://blog.trezor.io/trezor-integration-with-myetherwallet-3e217a652e08">
|
||||
<NewTabLink href="https://support.mycrypto.com/accessing-your-wallet/how-to-use-your-trezor-with-mycrypto.html">
|
||||
How to use TREZOR with MyCrypto
|
||||
</NewTabLink>
|
||||
</div>
|
||||
|
@ -97,18 +97,18 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private handlePathChange = (dPath: string) => {
|
||||
private handlePathChange = (dPath: DPath) => {
|
||||
this.setState({ dPath });
|
||||
this.handleConnect(dPath);
|
||||
};
|
||||
|
||||
private handleConnect = (dPath: string = this.state.dPath): void => {
|
||||
private handleConnect = (dPath: DPath): void => {
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: null
|
||||
});
|
||||
|
||||
TrezorWallet.getChainCode(dPath)
|
||||
TrezorWallet.getChainCode(dPath.value)
|
||||
.then(res => {
|
||||
this.setState({
|
||||
dPath,
|
||||
|
@ -130,17 +130,19 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
private handleUnlock = (address: string, index: number) => {
|
||||
this.props.onUnlock(new TrezorWallet(address, this.state.dPath, index));
|
||||
this.props.onUnlock(new TrezorWallet(address, this.state.dPath.value, index));
|
||||
this.reset();
|
||||
};
|
||||
|
||||
private handleNullConnect = (): void => this.handleConnect();
|
||||
private handleNullConnect = (): void => {
|
||||
this.handleConnect(this.state.dPath);
|
||||
};
|
||||
|
||||
private reset() {
|
||||
this.setState({
|
||||
publicKey: '',
|
||||
chainCode: '',
|
||||
dPath: this.props.dPath ? this.props.dPath.value : ''
|
||||
dPath: this.props.dPath || this.props.dPaths[0]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
};
|
||||
}
|
|
@ -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} />
|
||||
);
|
||||
}
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
export * from './Conditional';
|
|
@ -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';
|
||||
|
@ -22,5 +21,6 @@ export { default as GenerateKeystoreModal } from './GenerateKeystoreModal';
|
|||
export { default as TransactionStatus } from './TransactionStatus';
|
||||
export { default as ParityQrSigner } from './ParityQrSigner';
|
||||
export { default as ElectronNav } from './ElectronNav';
|
||||
export { default as AddressBookTable } from './AddressBookTable';
|
||||
export { default as Errorable } from './Errorable';
|
||||
export { default as AppAlphaNotice } from './AppAlphaNotice';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
|
@ -10,7 +10,7 @@ interface Props {
|
|||
|
||||
export default function Identicon(props: Props) {
|
||||
const size = props.size || '4rem';
|
||||
const { address, className } = props;
|
||||
const { address, className = '' } = props;
|
||||
// FIXME breaks on failed checksums
|
||||
const identiconDataUrl = isValidETHAddress(address) ? makeBlockie(address) : '';
|
||||
return (
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,74 @@
|
|||
import React, { HTMLProps } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import './Input.scss';
|
||||
|
||||
interface OwnProps extends HTMLProps<HTMLInputElement> {
|
||||
showInvalidBeforeBlur?: boolean;
|
||||
setInnerRef?(ref: HTMLInputElement | null): void;
|
||||
}
|
||||
|
||||
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 {
|
||||
setInnerRef,
|
||||
showInvalidBeforeBlur,
|
||||
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`,
|
||||
(showInvalidBeforeBlur || this.state.hasBlurred) && 'has-blurred',
|
||||
hasValue && 'has-value'
|
||||
);
|
||||
|
||||
return (
|
||||
<input
|
||||
{...this.props}
|
||||
{...htmlProps}
|
||||
ref={node => setInnerRef && setInnerRef(node)}
|
||||
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>) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -53,6 +53,10 @@ $m-anim-speed: 400ms;
|
|||
bottom: 4.5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
&.has-no-footer {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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)`;
|
||||
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>
|
||||
);
|
||||
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class BroadcastTx extends Component<Props> {
|
|||
<Input
|
||||
type="text"
|
||||
placeholder="0xf86b0284ee6b2800825208944bbeeb066ed09b7aed07bf39eee0460dfa26152088016345785d8a00008029a03ba7a0cc6d1756cd771f2119cf688b6d4dc9d37096089f0331fe0de0d1cc1254a02f7bcd19854c8d46f8de09e457aec25b127ab4328e1c0d24bfbff8702ee1f474"
|
||||
className={stateTransaction ? '' : 'invalid'}
|
||||
isValid={!!stateTransaction}
|
||||
value={userInput}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
||||
|
|
|
@ -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: '' });
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue