Merge pull request #1226 from MyCryptoHQ/develop
Tag Beta Release 0.4.0
|
@ -56,3 +56,5 @@ webpack_config/server.csr
|
|||
|
||||
v8-compile-cache-0/
|
||||
package-lock.json
|
||||
|
||||
yarn.lock
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Provider, connect } from 'react-redux';
|
||||
import { withRouter, Switch, Redirect, HashRouter, Route, BrowserRouter } from 'react-router-dom';
|
||||
// Components
|
||||
import Contracts from 'containers/Tabs/Contracts';
|
||||
|
@ -15,27 +15,41 @@ import PageNotFound from 'components/PageNotFound';
|
|||
import LogOutPrompt from 'components/LogOutPrompt';
|
||||
import { TitleBar } from 'components/ui';
|
||||
import { Store } from 'redux';
|
||||
import { pollOfflineStatus } from 'actions/config';
|
||||
import { pollOfflineStatus, TPollOfflineStatus } from 'actions/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { RouteNotFound } from 'components/RouteNotFound';
|
||||
import { RedirectWithQuery } from 'components/RedirectWithQuery';
|
||||
import 'what-input';
|
||||
import { setUnitMeta, TSetUnitMeta } from 'actions/transaction';
|
||||
import { getNetworkUnit } from 'selectors/config';
|
||||
|
||||
interface Props {
|
||||
interface OwnProps {
|
||||
store: Store<AppState>;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
networkUnit: string;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
pollOfflineStatus: TPollOfflineStatus;
|
||||
setUnitMeta: TSetUnitMeta;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
interface State {
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export default class Root extends Component<Props, State> {
|
||||
class RootClass extends Component<Props, State> {
|
||||
public state = {
|
||||
error: null
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.store.dispatch(pollOfflineStatus());
|
||||
this.props.pollOfflineStatus();
|
||||
this.props.setUnitMeta(this.props.networkUnit);
|
||||
}
|
||||
|
||||
public componentDidCatch(error: Error) {
|
||||
|
@ -134,3 +148,14 @@ const LegacyRoutes = withRouter(props => {
|
|||
</Switch>
|
||||
);
|
||||
});
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
networkUnit: getNetworkUnit(state)
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
pollOfflineStatus,
|
||||
setUnitMeta
|
||||
})(RootClass);
|
||||
|
|
|
@ -48,6 +48,14 @@ export function changeNodeIntent(payload: string): interfaces.ChangeNodeIntentAc
|
|||
};
|
||||
}
|
||||
|
||||
export type TChangeNodeForce = typeof changeNodeForce;
|
||||
export function changeNodeForce(payload: string): interfaces.ChangeNodeForceAction {
|
||||
return {
|
||||
type: TypeKeys.CONFIG_NODE_CHANGE_FORCE,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export type TAddCustomNode = typeof addCustomNode;
|
||||
export function addCustomNode(
|
||||
payload: interfaces.AddCustomNodeAction['payload']
|
||||
|
|
|
@ -36,6 +36,11 @@ export interface ChangeNodeIntentAction {
|
|||
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT;
|
||||
payload: string;
|
||||
}
|
||||
/*** Force Change Node ***/
|
||||
export interface ChangeNodeForceAction {
|
||||
type: TypeKeys.CONFIG_NODE_CHANGE_FORCE;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
/*** Add Custom Node ***/
|
||||
export interface AddCustomNodeAction {
|
||||
|
|
|
@ -10,6 +10,7 @@ export enum TypeKeys {
|
|||
CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET',
|
||||
CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE',
|
||||
CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT',
|
||||
CONFIG_NODE_CHANGE_FORCE = 'CONFIG_NODE_CHANGE_FORCE',
|
||||
|
||||
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE',
|
||||
CONFIG_REMOVE_CUSTOM_NODE = 'CONFIG_REMOVE_CUSTOM_NODE',
|
||||
|
|
|
@ -27,10 +27,10 @@ export function loadBityRatesSucceededSwap(
|
|||
};
|
||||
}
|
||||
|
||||
export type TLoadShapeshiftSucceededSwap = typeof loadShapeshiftRatesSucceededSwap;
|
||||
export type TLoadShapeshiftRatesSucceededSwap = typeof loadShapeshiftRatesSucceededSwap;
|
||||
export function loadShapeshiftRatesSucceededSwap(
|
||||
payload
|
||||
): interfaces.LoadShapshiftRatesSucceededSwapAction {
|
||||
): interfaces.LoadShapeshiftRatesSucceededSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED,
|
||||
payload
|
||||
|
@ -59,13 +59,27 @@ export function loadBityRatesRequestedSwap(): interfaces.LoadBityRatesRequestedS
|
|||
};
|
||||
}
|
||||
|
||||
export type TLoadShapeshiftRequestedSwap = typeof loadShapeshiftRatesRequestedSwap;
|
||||
export function loadShapeshiftRatesRequestedSwap(): interfaces.LoadShapeshiftRequestedSwapAction {
|
||||
export type TLoadShapeshiftRatesRequestedSwap = typeof loadShapeshiftRatesRequestedSwap;
|
||||
export function loadShapeshiftRatesRequestedSwap(): interfaces.LoadShapeshiftRatesRequestedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED
|
||||
};
|
||||
}
|
||||
|
||||
export type TLoadBityRatesFailedSwap = typeof loadBityRatesFailedSwap;
|
||||
export function loadBityRatesFailedSwap(): interfaces.LoadBityRatesFailedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_BITY_RATES_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
export type TLoadShapeshiftFailedSwap = typeof loadShapeshiftRatesFailedSwap;
|
||||
export function loadShapeshiftRatesFailedSwap(): interfaces.LoadShapeshiftRatesFailedSwapAction {
|
||||
return {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
export type TStopLoadBityRatesSwap = typeof stopLoadBityRatesSwap;
|
||||
export function stopLoadBityRatesSwap(): interfaces.StopLoadBityRatesSwapAction {
|
||||
return {
|
||||
|
|
|
@ -8,7 +8,7 @@ export interface Pairs {
|
|||
}
|
||||
|
||||
export interface SwapInput {
|
||||
id: string;
|
||||
label: string;
|
||||
amount: number | string;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ export interface LoadBityRatesSucceededSwapAction {
|
|||
payload: ApiResponse;
|
||||
}
|
||||
|
||||
export interface LoadShapshiftRatesSucceededSwapAction {
|
||||
export interface LoadShapeshiftRatesSucceededSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED;
|
||||
payload: ApiResponse;
|
||||
}
|
||||
|
@ -59,12 +59,18 @@ export interface RestartSwapAction {
|
|||
|
||||
export interface LoadBityRatesRequestedSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_BITY_RATES_REQUESTED;
|
||||
payload?: null;
|
||||
}
|
||||
|
||||
export interface LoadShapeshiftRequestedSwapAction {
|
||||
export interface LoadShapeshiftRatesRequestedSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED;
|
||||
payload?: null;
|
||||
}
|
||||
|
||||
export interface LoadBityRatesFailedSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_BITY_RATES_FAILED;
|
||||
}
|
||||
|
||||
export interface LoadShapeshiftRatesFailedSwapAction {
|
||||
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_FAILED;
|
||||
}
|
||||
|
||||
export interface ChangeStepSwapAction {
|
||||
|
@ -240,12 +246,14 @@ export interface ShowLiteSendAction {
|
|||
export type SwapAction =
|
||||
| ChangeStepSwapAction
|
||||
| InitSwap
|
||||
| LoadBityRatesSucceededSwapAction
|
||||
| LoadShapshiftRatesSucceededSwapAction
|
||||
| DestinationAddressSwapAction
|
||||
| RestartSwapAction
|
||||
| LoadBityRatesRequestedSwapAction
|
||||
| LoadShapeshiftRequestedSwapAction
|
||||
| LoadBityRatesSucceededSwapAction
|
||||
| LoadBityRatesFailedSwapAction
|
||||
| LoadShapeshiftRatesRequestedSwapAction
|
||||
| LoadShapeshiftRatesSucceededSwapAction
|
||||
| LoadShapeshiftRatesFailedSwapAction
|
||||
| StopLoadBityRatesSwapAction
|
||||
| StopLoadShapeshiftRatesSwapAction
|
||||
| BityOrderCreateRequestedSwapAction
|
||||
|
|
|
@ -7,6 +7,8 @@ export enum TypeKeys {
|
|||
SWAP_RESTART = 'SWAP_RESTART',
|
||||
SWAP_LOAD_BITY_RATES_REQUESTED = 'SWAP_LOAD_BITY_RATES_REQUESTED',
|
||||
SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED = 'SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED',
|
||||
SWAP_LOAD_BITY_RATES_FAILED = 'SWAP_LOAD_BITY_RATES_FAILED',
|
||||
SWAP_LOAD_SHAPESHIFT_RATES_FAILED = 'SWAP_LOAD_SHAPESHIFT_RATES_FAILED',
|
||||
SWAP_STOP_LOAD_BITY_RATES = 'SWAP_STOP_LOAD_BITY_RATES',
|
||||
SWAP_STOP_LOAD_SHAPESHIFT_RATES = 'SWAP_STOP_LOAD_SHAPESHIFT_RATES',
|
||||
SWAP_ORDER_TIME = 'SWAP_ORDER_TIME',
|
||||
|
|
|
@ -81,7 +81,10 @@ const setGasPriceField = (payload: SetGasPriceFieldAction['payload']): SetGasPri
|
|||
});
|
||||
|
||||
type TReset = typeof reset;
|
||||
const reset = (): ResetAction => ({ type: TypeKeys.RESET });
|
||||
const reset = (payload: ResetAction['payload'] = { include: {}, exclude: {} }): ResetAction => ({
|
||||
type: TypeKeys.RESET,
|
||||
payload
|
||||
});
|
||||
|
||||
export {
|
||||
TInputGasLimit,
|
||||
|
|
|
@ -5,23 +5,22 @@ import {
|
|||
SetTokenToMetaAction
|
||||
} from 'actions/transaction';
|
||||
|
||||
type TSetTokenBalance = typeof setTokenValue;
|
||||
type TSetUnitMeta = typeof setUnitMeta;
|
||||
type TSetTokenTo = typeof setTokenTo;
|
||||
|
||||
const setTokenTo = (payload: SetTokenToMetaAction['payload']): SetTokenToMetaAction => ({
|
||||
export type TSetTokenTo = typeof setTokenTo;
|
||||
export const setTokenTo = (payload: SetTokenToMetaAction['payload']): SetTokenToMetaAction => ({
|
||||
type: TypeKeys.TOKEN_TO_META_SET,
|
||||
payload
|
||||
});
|
||||
|
||||
const setTokenValue = (payload: SetTokenValueMetaAction['payload']): SetTokenValueMetaAction => ({
|
||||
export type TSetTokenValue = typeof setTokenValue;
|
||||
export const setTokenValue = (
|
||||
payload: SetTokenValueMetaAction['payload']
|
||||
): SetTokenValueMetaAction => ({
|
||||
type: TypeKeys.TOKEN_VALUE_META_SET,
|
||||
payload
|
||||
});
|
||||
|
||||
const setUnitMeta = (payload: SetUnitMetaAction['payload']): SetUnitMetaAction => ({
|
||||
export type TSetUnitMeta = typeof setUnitMeta;
|
||||
export const setUnitMeta = (payload: SetUnitMetaAction['payload']): SetUnitMetaAction => ({
|
||||
type: TypeKeys.UNIT_META_SET,
|
||||
payload
|
||||
});
|
||||
|
||||
export { TSetUnitMeta, TSetTokenBalance, TSetTokenTo, setUnitMeta, setTokenValue, setTokenTo };
|
||||
|
|
|
@ -7,6 +7,9 @@ import { SignAction } from './sign';
|
|||
import { SwapAction } from './swap';
|
||||
import { CurrentAction } from './current';
|
||||
import { SendEverythingAction } from './sendEverything';
|
||||
import { State as FieldState } from 'reducers/transaction/fields';
|
||||
import { State as MetaState } from 'reducers/transaction/meta';
|
||||
import { State as SignState } from 'reducers/transaction/sign';
|
||||
|
||||
export * from './broadcast';
|
||||
export * from './fields';
|
||||
|
@ -19,6 +22,18 @@ export * from './sendEverything';
|
|||
|
||||
export interface ResetAction {
|
||||
type: TypeKeys.RESET;
|
||||
payload: {
|
||||
include: {
|
||||
fields?: (keyof FieldState)[];
|
||||
meta?: (keyof MetaState)[];
|
||||
sign?: (keyof SignState)[];
|
||||
};
|
||||
exclude: {
|
||||
fields?: (keyof FieldState)[];
|
||||
meta?: (keyof MetaState)[];
|
||||
sign?: (keyof SignState)[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type TransactionAction =
|
||||
|
|
|
@ -17,6 +17,7 @@ export interface GasEstimates {
|
|||
fast: number;
|
||||
fastest: number;
|
||||
time: number;
|
||||
chainId: number;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,7 @@ export function fetchGasEstimates(): Promise<GasEstimates> {
|
|||
.then((res: RawGasEstimates) => ({
|
||||
...res,
|
||||
time: Date.now(),
|
||||
chainId: 1,
|
||||
isDefault: false
|
||||
}));
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 47 KiB |
|
@ -1,3 +0,0 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='20px' height='20px' viewBox='0 0 79.536 79.536' style='enable-background:new 0 0 79.536 79.536;' xml:space='preserve'><path fill='#999' d='M39.769,0C17.8,0,0,17.8,0,39.768c0,21.965,17.8,39.768,39.769,39.768 c21.965,0,39.768-17.803,39.768-39.768C79.536,17.8,61.733,0,39.769,0z M34.142,58.513L15.397,39.768l7.498-7.498l11.247,11.247 l22.497-22.493l7.498,7.498L34.142,58.513z'/></svg>
|
Before Width: | Height: | Size: 602 B |
|
@ -1,3 +0,0 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="20px" viewBox="0 0 528.899 528.899" style="enable-background:new 0 0 528.899 528.899;" xml:space="preserve"><path fill="#21a4ce" d="M328.883,89.125l107.59,107.589l-272.34,272.34L56.604,361.465L328.883,89.125z M518.113,63.177l-47.981-47.981 c-18.543-18.543-48.653-18.543-67.259,0l-45.961,45.961l107.59,107.59l53.611-53.611 C532.495,100.753,532.495,77.559,518.113,63.177z M0.3,512.69c-1.958,8.812,5.998,16.708,14.811,14.565l119.891-29.069 L27.473,390.597L0.3,512.69z"/></svg>
|
Before Width: | Height: | Size: 761 B |
|
@ -1 +0,0 @@
|
|||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 92"><path fill="#ECECEC" d="M45.386.004C19.983.344-.333 21.215.005 46.619c.34 25.393 21.209 45.715 46.611 45.377 25.398-.342 45.718-21.213 45.38-46.615-.34-25.395-21.21-45.716-46.61-45.377zM45.25 74l-.254-.004c-3.912-.116-6.67-2.998-6.559-6.852.109-3.788 2.934-6.538 6.717-6.538l.227.004c4.021.119 6.748 2.972 6.635 6.937C51.904 71.346 49.123 74 45.25 74zm16.455-32.659c-.92 1.307-2.943 2.93-5.492 4.916l-2.807 1.938c-1.541 1.198-2.471 2.325-2.82 3.434-.275.873-.41 1.104-.434 2.88l-.004.451H39.43l.031-.907c.131-3.728.223-5.921 1.768-7.733 2.424-2.846 7.771-6.289 7.998-6.435.766-.577 1.412-1.234 1.893-1.936 1.125-1.551 1.623-2.772 1.623-3.972a7.74 7.74 0 0 0-1.471-4.576c-.939-1.323-2.723-1.993-5.303-1.993-2.559 0-4.311.812-5.359 2.478-1.078 1.713-1.623 3.512-1.623 5.35v.457H27.936l.02-.477c.285-6.769 2.701-11.643 7.178-14.487C37.947 18.918 41.447 18 45.531 18c5.346 0 9.859 1.299 13.412 3.861 3.6 2.596 5.426 6.484 5.426 11.556 0 2.837-.896 5.502-2.664 7.924z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB |
|
@ -1 +1 @@
|
|||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 92"><path fill="#0e97c0" d="M45.386.004C19.983.344-.333 21.215.005 46.619c.34 25.393 21.209 45.715 46.611 45.377 25.398-.342 45.718-21.213 45.38-46.615-.34-25.395-21.21-45.716-46.61-45.377zM45.25 74l-.254-.004c-3.912-.116-6.67-2.998-6.559-6.852.109-3.788 2.934-6.538 6.717-6.538l.227.004c4.021.119 6.748 2.972 6.635 6.937C51.904 71.346 49.123 74 45.25 74zm16.455-32.659c-.92 1.307-2.943 2.93-5.492 4.916l-2.807 1.938c-1.541 1.198-2.471 2.325-2.82 3.434-.275.873-.41 1.104-.434 2.88l-.004.451H39.43l.031-.907c.131-3.728.223-5.921 1.768-7.733 2.424-2.846 7.771-6.289 7.998-6.435.766-.577 1.412-1.234 1.893-1.936 1.125-1.551 1.623-2.772 1.623-3.972a7.74 7.74 0 0 0-1.471-4.576c-.939-1.323-2.723-1.993-5.303-1.993-2.559 0-4.311.812-5.359 2.478-1.078 1.713-1.623 3.512-1.623 5.35v.457H27.936l.02-.477c.285-6.769 2.701-11.643 7.178-14.487C37.947 18.918 41.447 18 45.531 18c5.346 0 9.859 1.299 13.412 3.861 3.6 2.596 5.426 6.484 5.426 11.556 0 2.837-.896 5.502-2.664 7.924z"/></svg>
|
||||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 92"><path fill="#007896" d="M45.386.004C19.983.344-.333 21.215.005 46.619c.34 25.393 21.209 45.715 46.611 45.377 25.398-.342 45.718-21.213 45.38-46.615-.34-25.395-21.21-45.716-46.61-45.377zM45.25 74l-.254-.004c-3.912-.116-6.67-2.998-6.559-6.852.109-3.788 2.934-6.538 6.717-6.538l.227.004c4.021.119 6.748 2.972 6.635 6.937C51.904 71.346 49.123 74 45.25 74zm16.455-32.659c-.92 1.307-2.943 2.93-5.492 4.916l-2.807 1.938c-1.541 1.198-2.471 2.325-2.82 3.434-.275.873-.41 1.104-.434 2.88l-.004.451H39.43l.031-.907c.131-3.728.223-5.921 1.768-7.733 2.424-2.846 7.771-6.289 7.998-6.435.766-.577 1.412-1.234 1.893-1.936 1.125-1.551 1.623-2.772 1.623-3.972a7.74 7.74 0 0 0-1.471-4.576c-.939-1.323-2.723-1.993-5.303-1.993-2.559 0-4.311.812-5.359 2.478-1.078 1.713-1.623 3.512-1.623 5.35v.457H27.936l.02-.477c.285-6.769 2.701-11.643 7.178-14.487C37.947 18.918 41.447 18 45.531 18c5.346 0 9.859 1.299 13.412 3.861 3.6 2.596 5.426 6.484 5.426 11.556 0 2.837-.896 5.502-2.664 7.924z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
@ -1 +0,0 @@
|
|||
<svg version="1.1" width="20px" height="20px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 92 92" style="enable-background:new 0 0 92 92;" xml:space="preserve"><path fill="#2bb2dd" d="M45.386,0.004C19.983,0.344-0.333,21.215,0.005,46.619c0.34,25.393,21.209,45.715,46.611,45.377 c25.398-0.342,45.718-21.213,45.38-46.615C91.656,19.986,70.786-0.335,45.386,0.004z M45.25,74l-0.254-0.004 c-3.912-0.116-6.67-2.998-6.559-6.852c0.109-3.788,2.934-6.538,6.717-6.538l0.227,0.004c4.021,0.119,6.748,2.972,6.635,6.937 C51.904,71.346,49.123,74,45.25,74z M61.705,41.341c-0.92,1.307-2.943,2.93-5.492,4.916l-2.807,1.938 c-1.541,1.198-2.471,2.325-2.82,3.434c-0.275,0.873-0.41,1.104-0.434,2.88l-0.004,0.451H39.43l0.031-0.907 c0.131-3.728,0.223-5.921,1.768-7.733c2.424-2.846,7.771-6.289,7.998-6.435c0.766-0.577,1.412-1.234,1.893-1.936 c1.125-1.551,1.623-2.772,1.623-3.972c0-1.665-0.494-3.205-1.471-4.576c-0.939-1.323-2.723-1.993-5.303-1.993 c-2.559,0-4.311,0.812-5.359,2.478c-1.078,1.713-1.623,3.512-1.623,5.35v0.457H27.936l0.02-0.477 c0.285-6.769,2.701-11.643,7.178-14.487C37.947,18.918,41.447,18,45.531,18c5.346,0,9.859,1.299,13.412,3.861 c3.6,2.596,5.426,6.484,5.426,11.556C64.369,36.254,63.473,38.919,61.705,41.341z"/></svg>
|
Before Width: | Height: | Size: 1.2 KiB |
|
@ -1,3 +0,0 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='20px' height='20px' viewBox='0 0 511.626 511.626' style='enable-background:new 0 0 511.626 511.626;' xml:space='preserve'><path fill='#f0ad4e' d='M505.918,236.117c-26.651-43.587-62.485-78.609-107.497-105.065c-45.015-26.457-92.549-39.687-142.608-39.687 c-50.059,0-97.595,13.225-142.61,39.687C68.187,157.508,32.355,192.53,5.708,236.117C1.903,242.778,0,249.345,0,255.818 c0,6.473,1.903,13.04,5.708,19.699c26.647,43.589,62.479,78.614,107.495,105.064c45.015,26.46,92.551,39.68,142.61,39.68 c50.06,0,97.594-13.176,142.608-39.536c45.012-26.361,80.852-61.432,107.497-105.208c3.806-6.659,5.708-13.223,5.708-19.699 C511.626,249.345,509.724,242.778,505.918,236.117z M194.568,158.03c17.034-17.034,37.447-25.554,61.242-25.554 c3.805,0,7.043,1.336,9.709,3.999c2.662,2.664,4,5.901,4,9.707c0,3.809-1.338,7.044-3.994,9.704 c-2.662,2.667-5.902,3.999-9.708,3.999c-16.368,0-30.362,5.808-41.971,17.416c-11.613,11.615-17.416,25.603-17.416,41.971 c0,3.811-1.336,7.044-3.999,9.71c-2.667,2.668-5.901,3.999-9.707,3.999c-3.809,0-7.044-1.334-9.71-3.999 c-2.667-2.666-3.999-5.903-3.999-9.71C169.015,195.482,177.535,175.065,194.568,158.03z M379.867,349.04 c-38.164,23.12-79.514,34.687-124.054,34.687c-44.539,0-85.889-11.56-124.051-34.687s-69.901-54.2-95.215-93.222 c28.931-44.921,65.19-78.518,108.777-100.783c-11.61,19.792-17.417,41.207-17.417,64.236c0,35.216,12.517,65.329,37.544,90.362 s55.151,37.544,90.362,37.544c35.214,0,65.329-12.518,90.362-37.544s37.545-55.146,37.545-90.362 c0-23.029-5.808-44.447-17.419-64.236c43.585,22.265,79.846,55.865,108.776,100.783C449.767,294.84,418.031,325.913,379.867,349.04 z'/></svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' width='20px' height='20px'
|
||||
xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 27.965 27.965'
|
||||
style='enable-background:new 0 0 27.965 27.965;'>
|
||||
<path fill='#999'
|
||||
d='M13.98,0C6.259,0,0,6.261,0,13.983c0,7.721,6.259,13.982,13.98,13.982c7.725,0,13.985-6.262,13.985-13.982 C27.965,6.261,21.705,0,13.98,0z M19.992,17.769l-2.227,2.224c0,0-3.523-3.78-3.786-3.78c-0.259,0-3.783,3.78-3.783,3.78 l-2.228-2.224c0,0,3.784-3.472,3.784-3.781c0-0.314-3.784-3.787-3.784-3.787l2.228-2.229c0,0,3.553,3.782,3.783,3.782 c0.232,0,3.786-3.782,3.786-3.782l2.227,2.229c0,0-3.785,3.523-3.785,3.787C16.207,14.239,19.992,17.769,19.992,17.769z'/>
|
||||
</svg>
|
Before Width: | Height: | Size: 866 B |
Before Width: | Height: | Size: 2.8 KiB |
|
@ -8,7 +8,7 @@
|
|||
<g id="Artboard" fill-rule="nonzero">
|
||||
<g id="logo-mycrypto">
|
||||
<g id="Group">
|
||||
<circle id="Oval" fill="#0e97c0" cx="120.18" cy="121" r="120.18"></circle>
|
||||
<circle id="Oval" fill="#007896" cx="120.18" cy="121" r="120.18"></circle>
|
||||
<g transform="translate(39.000000, 51.000000)" fill="#FFFFFF" id="Shape">
|
||||
<path d="M93.7,100.26 C90.4608521,103.553832 86.034676,105.408954 81.415,105.408954 C76.795324,105.408954 72.3691479,103.553832 69.13,100.26 L53.47,84.43 C51.4545763,82.3796602 48.700035,81.2248104 45.825,81.2248104 C42.949965,81.2248104 40.1954237,82.3796602 38.18,84.43 C31.3962777,91.3322169 31.3962777,102.397783 38.18,109.3 L69.18,140.59 C72.4191479,143.883832 76.845324,145.738954 81.465,145.738954 C86.084676,145.738954 90.5108521,143.883832 93.75,140.59 L124.67,109.26 C131.453722,102.357783 131.453722,91.2922169 124.67,84.39 C122.654576,82.3396602 119.900035,81.1848104 117.025,81.1848104 C114.149965,81.1848104 111.395424,82.3396602 109.38,84.39 L93.7,100.26 Z"></path>
|
||||
<g>
|
||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
@ -54,7 +54,7 @@
|
|||
<path d="M82.77,744.06 L87.55,710.06 C87.8071361,708.226119 86.9908966,706.407071 85.45,705.38 C78.3717357,700.699197 75.198423,691.93208 77.6408657,683.805198 C80.0833084,675.678315 87.5640273,670.113147 96.05,670.11 C96.64,670.11 97.2,670.23 97.78,670.28 C88.9158298,664.439589 76.9954116,666.89083 71.1550005,675.755 C65.3145894,684.61917 67.7658301,696.539589 76.63,702.38 C78.1708966,703.407071 78.9871361,705.226119 78.73,707.06 L73.53,744.06 L82.77,744.06 Z" id="Shape" fill="#000000" opacity="0.1"></path>
|
||||
<g id="mycrypto__circ__blue-on-navy__64" transform="translate(116.000000, 0.000000)">
|
||||
<g id="Oval">
|
||||
<use fill="#0E97C0" fill-rule="evenodd" xlink:href="#path-1"></use>
|
||||
<use fill="#007896" fill-rule="evenodd" xlink:href="#path-1"></use>
|
||||
<circle stroke="#000000" stroke-width="10" cx="145" cy="145" r="140"></circle>
|
||||
</g>
|
||||
<g id="Shape">
|
||||
|
@ -69,4 +69,4 @@
|
|||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
@ -32,7 +32,7 @@
|
|||
<rect id="Rectangle-path" fill="#FFCE00" x="40.72" y="256.52" width="16" height="48"></rect>
|
||||
</g>
|
||||
<g id="mycrypto__circ__blue-on-navy__64" transform="translate(216.000000, 191.000000)">
|
||||
<circle id="Oval" fill="#0E97C0" cx="37" cy="37" r="37"></circle>
|
||||
<circle id="Oval" fill="#007896" cx="37" cy="37" r="37"></circle>
|
||||
<g id="Shape">
|
||||
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
|
||||
<path stroke="#000000" stroke-width="2" d="M48.7644965,40.0113785 C49.7178355,40.010501 50.6345076,40.3752222 51.3145109,41.0314555 C53.5618297,43.2182135 53.5618297,46.7390341 51.3110373,48.9291558 L41.5069096,58.3771804 C40.4445637,59.4168045 38.9938635,60.0003162 37.4823951,59.9999999 C35.9705083,60.0003162 34.5198081,59.4168045 33.4616659,58.3812702 L23.6854891,48.9257921 C21.4381703,46.7390341 21.4381703,43.2182135 23.6838532,41.033051 C24.3665268,40.3657303 25.2975124,39.9945561 26.2579466,40.0000451 C27.2198046,39.9987497 28.1441246,40.3696137 28.8202797,41.0305404 L33.7601132,45.8246431 C34.7441958,46.7780599 36.0942246,47.317442 37.505938,47.317442 C38.9176514,47.317442 40.2676802,46.7780599 41.2528603,45.8235816 L46.2009116,41.0443696 C46.8793196,40.3812496 47.8031089,40.0104213 48.7644965,40.0113785 Z"></path>
|
||||
|
@ -44,4 +44,4 @@
|
|||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 70 KiB |
|
@ -13,7 +13,7 @@
|
|||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<circle stroke="#0E97C0" stroke-width="20" cx="170" cy="170" r="160"></circle>
|
||||
<circle stroke="#007896" stroke-width="20" cx="170" cy="170" r="160"></circle>
|
||||
<rect id="Rectangle" fill="#FFE14D" mask="url(#mask-2)" x="-1.13333333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<rect id="Rectangle" fill="#FFCC33" mask="url(#mask-2)" x="171.133333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<circle id="Oval-2" stroke="#333333" stroke-width="20" mask="url(#mask-2)" cx="170" cy="170" r="160"></circle>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<mask id="mask-4" fill="white">
|
||||
<use xlink:href="#path-3"></use>
|
||||
</mask>
|
||||
<circle stroke="#0E97C0" stroke-width="20" cx="170" cy="170" r="160"></circle>
|
||||
<circle stroke="#007896" stroke-width="20" cx="170" cy="170" r="160"></circle>
|
||||
<rect id="Rectangle" fill="#6EA6E8" mask="url(#mask-4)" x="-1.13333333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<rect id="Rectangle" fill="#5C9BE4" mask="url(#mask-4)" x="171.133333" y="1.13333333" width="173.4" height="340"></rect>
|
||||
<circle id="Oval-2" stroke="#333333" stroke-width="20" mask="url(#mask-4)" cx="170" cy="170" r="160"></circle>
|
||||
|
@ -41,4 +41,4 @@
|
|||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import { AddressFieldFactory } from './AddressFieldFactory';
|
||||
import { donationAddressMap } from 'config';
|
||||
import translate from 'translations';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
isReadOnly?: boolean;
|
||||
|
@ -9,16 +11,20 @@ interface Props {
|
|||
export const AddressField: React.SFC<Props> = ({ isReadOnly }) => (
|
||||
<AddressFieldFactory
|
||||
withProps={({ currentTo, isValid, onChange, readOnly }) => (
|
||||
<React.Fragment>
|
||||
<input
|
||||
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
value={currentTo.raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('SEND_addr')}</div>
|
||||
<Input
|
||||
className={`input-group-input ${isValid ? '' : 'invalid'}`}
|
||||
type="text"
|
||||
value={currentTo.raw}
|
||||
placeholder={donationAddressMap.ETH}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
spellCheck={false}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Identicon, Spinner } from 'components/ui';
|
||||
import translate from 'translations';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { ICurrentTo, getCurrentTo, isValidCurrentTo } from 'selectors/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -49,7 +48,6 @@ class AddressInputFactoryClass extends Component<Props> {
|
|||
return (
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-11">
|
||||
<label>{translate('SEND_addr')}:</label>
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) =>
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
import React from 'react';
|
||||
import './index.scss';
|
||||
|
||||
const LS_KEY = 'acknowledged-alpha';
|
||||
|
||||
interface State {
|
||||
isFading: boolean;
|
||||
hasAcknowledged: boolean;
|
||||
}
|
||||
export default class AlphaAgreement 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={`AlphaAgreement ${isFading}`}>
|
||||
<div className="AlphaAgreement-content">
|
||||
<h2>This is an Unstable Version of MyCrypto</h2>
|
||||
<p>
|
||||
You are about to access a beta version of MyCrypto that is currently in development. In
|
||||
its current state, it should only be used for testing, not for important transactions.
|
||||
</p>
|
||||
<p>
|
||||
Any wallets you generate should not hold a significant value, and any transactions you
|
||||
make should be for small amounts. MyCrypto does not claim responsibility for any issues
|
||||
that happen while using the beta version.
|
||||
</p>
|
||||
<p>Are you sure you would like to continue?</p>
|
||||
|
||||
<div className="AlphaAgreement-content-buttons">
|
||||
<button className="AlphaAgreement-content-buttons-btn is-reject" onClick={this.reject}>
|
||||
No, take me to prod
|
||||
</button>
|
||||
<button
|
||||
className="AlphaAgreement-content-buttons-btn is-continue"
|
||||
onClick={this.doContinue}
|
||||
>
|
||||
Yes, continue to beta
|
||||
</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');
|
||||
};
|
||||
}
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { AmountFieldFactory } from './AmountFieldFactory';
|
||||
import { UnitDropDown } from 'components';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
hasUnitDropdown?: boolean;
|
||||
|
@ -16,12 +17,12 @@ export const AmountField: React.SFC<Props> = ({
|
|||
}) => (
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<React.Fragment>
|
||||
<label>{translate('SEND_amount')}</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
className={`form-control ${
|
||||
isAmountValid(raw, customValidator, isValid) ? 'is-valid' : 'is-invalid'
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<div className="input-group-header">{translate('SEND_amount')}</div>
|
||||
<Input
|
||||
className={`input-group-input ${
|
||||
isAmountValid(raw, customValidator, isValid) ? '' : 'invalid'
|
||||
}`}
|
||||
type="number"
|
||||
placeholder={translateRaw('SEND_amount_short')}
|
||||
|
@ -30,8 +31,8 @@ export const AmountField: React.SFC<Props> = ({
|
|||
onChange={onChange}
|
||||
/>
|
||||
{hasUnitDropdown && <UnitDropDown showAllTokens={showAllTokens} />}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -2,6 +2,28 @@
|
|||
@import 'common/sass/mixins';
|
||||
|
||||
.AccountInfo {
|
||||
&-copy {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
color: $text-color;
|
||||
font-size: $font-size-xs;
|
||||
opacity: 0.5;
|
||||
transition: $transition;
|
||||
|
||||
&:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.is-copied{
|
||||
color: $brand-success;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.fa {
|
||||
margin-right: $space-xs;
|
||||
}
|
||||
}
|
||||
|
||||
&-section {
|
||||
margin-top: $space * 1.5;
|
||||
|
||||
|
@ -63,6 +85,7 @@
|
|||
}
|
||||
|
||||
&-addr {
|
||||
margin-top: -$space-xs;
|
||||
word-wrap: break-word;
|
||||
@include mono;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { Identicon, UnitDisplay } from 'components/ui';
|
||||
import { IWallet, Balance, TrezorWallet, LedgerWallet } from 'libs/wallet';
|
||||
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 translate from 'translations';
|
||||
import './AccountInfo.scss';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { connect } from 'react-redux';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import { TSetAccountBalance, setAccountBalance } from 'actions/wallet';
|
||||
import './AccountInfo.scss';
|
||||
|
||||
interface OwnProps {
|
||||
wallet: IWallet;
|
||||
|
@ -24,6 +26,7 @@ interface State {
|
|||
showLongBalance: boolean;
|
||||
address: string;
|
||||
confirmAddr: boolean;
|
||||
copied: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -36,11 +39,12 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
public state = {
|
||||
showLongBalance: false,
|
||||
address: '',
|
||||
confirmAddr: false
|
||||
confirmAddr: false,
|
||||
copied: false
|
||||
};
|
||||
|
||||
public async setAddressFromWallet() {
|
||||
const address = await this.props.wallet.getAddressString();
|
||||
public setAddressFromWallet() {
|
||||
const address = this.props.wallet.getAddressString();
|
||||
if (address !== this.state.address) {
|
||||
this.setState({ address });
|
||||
}
|
||||
|
@ -69,6 +73,17 @@ 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 { address, showLongBalance, confirmAddr } = this.state;
|
||||
|
@ -89,7 +104,18 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
<Identicon address={address} size="100%" />
|
||||
</div>
|
||||
<div className="AccountInfo-address-wrapper">
|
||||
<div className="AccountInfo-address-addr">{address}</div>
|
||||
<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>
|
||||
|
||||
|
@ -160,24 +186,16 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
<ul className="AccountInfo-list">
|
||||
{!!blockExplorer && (
|
||||
<li className="AccountInfo-list-item">
|
||||
<a
|
||||
href={blockExplorer.addressUrl(address)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<NewTabLink href={blockExplorer.addressUrl(address)}>
|
||||
{`${network.name} (${blockExplorer.origin})`}
|
||||
</a>
|
||||
</NewTabLink>
|
||||
</li>
|
||||
)}
|
||||
{!!tokenExplorer && (
|
||||
<li className="AccountInfo-list-item">
|
||||
<a
|
||||
href={tokenExplorer.address(address)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<NewTabLink href={tokenExplorer.address(address)}>
|
||||
{`Tokens (${tokenExplorer.name})`}
|
||||
</a>
|
||||
</NewTabLink>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
&-fiat-symbol {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-right: 0.5rem;
|
||||
text-align: center;
|
||||
|
|
|
@ -81,11 +81,12 @@ class EquivalentValues extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
const { balance, tokenBalances, isOffline } = this.props;
|
||||
const { balance, tokenBalances, isOffline, network } = this.props;
|
||||
if (
|
||||
nextProps.balance !== balance ||
|
||||
nextProps.tokenBalances !== tokenBalances ||
|
||||
nextProps.isOffline !== isOffline
|
||||
nextProps.isOffline !== isOffline ||
|
||||
nextProps.network.unit !== network.unit
|
||||
) {
|
||||
const defaultOption = this.defaultOption(
|
||||
nextProps.balance,
|
||||
|
@ -288,7 +289,8 @@ class EquivalentValues extends React.Component<Props, State> {
|
|||
const currencies = tokenBalances
|
||||
.filter(tk => !tk.balance.isZero())
|
||||
.map(tk => tk.symbol)
|
||||
.sort();
|
||||
.sort()
|
||||
.concat([props.network.unit]);
|
||||
|
||||
// If it's the same currencies as we have, skip it
|
||||
if (this.requestedCurrencies && currencies.join() === this.requestedCurrencies.join()) {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { HELP_ARTICLE } from 'config';
|
||||
import { isPositiveIntegerOrZero, isValidETHAddress } from 'libs/validators';
|
||||
import translate from 'translations';
|
||||
import { HelpLink } from 'components/ui';
|
||||
import { HelpLink, Input } from 'components/ui';
|
||||
import './AddCustomTokenForm.scss';
|
||||
import { Token } from 'types/network';
|
||||
|
||||
|
@ -42,7 +41,6 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
|
|||
|
||||
public render() {
|
||||
const { address, symbol, decimal } = this.state;
|
||||
const inputClasses = 'AddCustom-field-input form-control input-sm';
|
||||
const errors = this.getErrors();
|
||||
|
||||
const fields = [
|
||||
|
@ -69,11 +67,10 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
|
|||
return (
|
||||
<label className="AddCustom-field form-group" key={field.name}>
|
||||
<span className="AddCustom-field-label">{field.label}</span>
|
||||
<input
|
||||
className={classnames(
|
||||
inputClasses,
|
||||
errors[field.name] ? 'is-invalid' : field.value ? 'is-valid' : ''
|
||||
)}
|
||||
<Input
|
||||
className={`${
|
||||
errors[field.name] ? 'invalid' : field.value ? 'valid' : ''
|
||||
} AddCustom-field-input input-sm`}
|
||||
type="text"
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
|
|
|
@ -1,43 +1,50 @@
|
|||
@import "common/sass/variables";
|
||||
@import "common/sass/mixins";
|
||||
|
||||
.AlphaAgreement {
|
||||
.BetaAgreement {
|
||||
@include cover-message;
|
||||
background: $brand-info;
|
||||
|
||||
&-content {
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-buttons {
|
||||
padding-top: 20px;
|
||||
|
||||
&-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
max-width: 280px;
|
||||
margin: 0 auto;
|
||||
border: none;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
transition: $transition;
|
||||
|
||||
&.is-reject {
|
||||
&.is-continue {
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
font-size: 22px;
|
||||
background: rgba(#fff, 0.96);
|
||||
color: $gray;
|
||||
color: $gray-dark;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
color: $gray-darker;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-continue {
|
||||
&.is-reject {
|
||||
background: none;
|
||||
color: #fff;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +58,7 @@
|
|||
background: #fff;
|
||||
transition: all 500ms ease 400ms;
|
||||
|
||||
.AlphaAgreement-content {
|
||||
.BetaAgreement-content {
|
||||
opacity: 0;
|
||||
transform: translateY(15px);
|
||||
transition: all 500ms ease;
|
|
@ -0,0 +1,72 @@
|
|||
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!</h2>
|
||||
<p>
|
||||
You are about to use a version of MyCrypto that hasn't been released yet. While we are
|
||||
confident that it is close to being production ready, you may want to use the current
|
||||
production site for larger or more time-sensitive transactions.
|
||||
</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
|
||||
</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');
|
||||
};
|
||||
}
|
|
@ -5,6 +5,7 @@ import { getCurrentTo, ICurrentTo } from 'selectors/transaction';
|
|||
import { getAllTokens } from 'selectors/config';
|
||||
import { getWalletInst } from 'selectors/wallet';
|
||||
import { getAddressMessage } from 'config';
|
||||
import { Address } from 'components/ui';
|
||||
import { Token } from 'types/network';
|
||||
|
||||
interface ReduxProps {
|
||||
|
@ -47,9 +48,9 @@ class CurrentCustomMessageClass extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private async setAddressState(props: Props) {
|
||||
private setAddressState(props: Props) {
|
||||
if (props.wallet) {
|
||||
const walletAddress = await props.wallet.getAddressString();
|
||||
const walletAddress = props.wallet.getAddressString();
|
||||
this.setState({ walletAddress });
|
||||
} else {
|
||||
this.setState({ walletAddress: '' });
|
||||
|
@ -72,7 +73,10 @@ class CurrentCustomMessageClass extends PureComponent<Props, State> {
|
|||
<React.Fragment>
|
||||
<p>
|
||||
<small>
|
||||
A message regarding <strong>{address}</strong>:
|
||||
A message regarding{' '}
|
||||
<strong>
|
||||
<Address address={address} />
|
||||
</strong>:
|
||||
</small>
|
||||
</p>
|
||||
<p>{msg.msg}</p>
|
||||
|
|
|
@ -2,21 +2,24 @@ import { DataFieldFactory } from './DataFieldFactory';
|
|||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { donationAddressMap } from 'config';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export const DataField: React.SFC<{}> = () => (
|
||||
<DataFieldFactory
|
||||
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
|
||||
<>
|
||||
<label>{translate('OFFLINE_Step2_Label_6')}</label>
|
||||
<input
|
||||
className={`form-control ${dataExists ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
placeholder={donationAddressMap.ETH}
|
||||
value={raw}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
<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'}
|
||||
type="text"
|
||||
placeholder={donationAddressMap.ETH}
|
||||
value={raw}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { NewTabLink } from 'components/ui';
|
||||
import './index.scss';
|
||||
|
||||
const SUBJECT = 'Error!';
|
||||
|
@ -24,14 +25,17 @@ const ErrorScreen: React.SFC<Props> = ({ error }) => {
|
|||
>
|
||||
support@mycrypto.com
|
||||
</a>{' '}
|
||||
if a refresh doesn't fix it (or click it anyway to open a ticket 😊). If you attach the
|
||||
following error, you'll make it a ton easier to fix the issue.
|
||||
if a refresh doesn't fix it (or click it anyway to open a ticket 😊). You can also submit
|
||||
an issue on our{' '}
|
||||
<NewTabLink href="https://github.com/MyCryptoHQ/MyCrypto/issues">
|
||||
GitHub Repository
|
||||
</NewTabLink>. Please attach the following error to help our team solve your issue.
|
||||
</p>
|
||||
<code>{error.message}</code>
|
||||
<h5>
|
||||
Please make sure the error message does not include any sensitive information before
|
||||
sending it us. We don't want your private keys!
|
||||
sending it to us. We don't want your private keys!
|
||||
</h5>
|
||||
<code>{error.message}</code>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
@import "common/sass/variables";
|
||||
@import 'common/sass/variables';
|
||||
|
||||
.pre-footer {
|
||||
padding: 1rem;
|
||||
box-shadow: 16px 16px 47px 0 rgba(0, 0, 0, .07);
|
||||
margin-top: 5rem;
|
||||
box-shadow: 16px 16px 47px 0 rgba(0, 0, 0, 0.07);
|
||||
margin-top: 2rem;
|
||||
background-color: white;
|
||||
text-align: center;
|
||||
|
||||
|
|
|
@ -158,10 +158,10 @@ $footer-link-color: #fff;
|
|||
.SocialMediaLink {
|
||||
transition: opacity 0.3s;
|
||||
color: #fff;
|
||||
margin: 0.65rem;
|
||||
margin: 0.55rem;
|
||||
|
||||
> i {
|
||||
font-size: 1.2rem;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
|
|
|
@ -5,7 +5,8 @@ import {
|
|||
ethercardReferralURL,
|
||||
donationAddressMap,
|
||||
VERSION,
|
||||
knowledgeBaseURL
|
||||
knowledgeBaseURL,
|
||||
discordURL
|
||||
} from 'config';
|
||||
import React from 'react';
|
||||
import PreFooter from './PreFooter';
|
||||
|
@ -46,6 +47,10 @@ const SOCIAL_MEDIA: Link[] = [
|
|||
{
|
||||
link: 'https://www.reddit.com/r/mycrypto/',
|
||||
text: 'reddit'
|
||||
},
|
||||
{
|
||||
link: discordURL,
|
||||
text: 'discord'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import translate from 'translations';
|
|||
import { gasLimitValidator } from 'libs/validators';
|
||||
import { InlineSpinner } from 'components/ui/InlineSpinner';
|
||||
import './GasLimitField.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
customLabel?: string;
|
||||
|
@ -13,22 +14,24 @@ interface Props {
|
|||
export const GasLimitField: React.SFC<Props> = ({ customLabel, disabled }) => (
|
||||
<GasLimitFieldFactory
|
||||
withProps={({ gasLimit: { raw }, onChange, readOnly, gasEstimationPending }) => (
|
||||
<React.Fragment>
|
||||
<div className="gaslimit-label-wrapper flex-wrapper">
|
||||
{customLabel ? <label>{customLabel} </label> : <label>{translate('TRANS_gas')} </label>}
|
||||
<div className="flex-spacer" />
|
||||
<InlineSpinner active={gasEstimationPending} text="Calculating" />
|
||||
</div>
|
||||
<input
|
||||
className={`form-control ${gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}`}
|
||||
type="number"
|
||||
placeholder="e.g. 21000"
|
||||
readOnly={!!readOnly}
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<div className="input-group-wrapper AdvancedGas-gas-price">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{customLabel ? customLabel : translate('TRANS_gas')}
|
||||
<div className="flex-spacer" />
|
||||
<InlineSpinner active={gasEstimationPending} text="Calculating" />
|
||||
</div>
|
||||
<Input
|
||||
className={gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}
|
||||
type="number"
|
||||
placeholder="e.g. 21000"
|
||||
readOnly={!!readOnly}
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -60,27 +60,31 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
|
|||
handleClose={this.handleClose}
|
||||
>
|
||||
<form className="GenKeystore" onSubmit={this.handleSubmit}>
|
||||
<label className="GenKeystore-field">
|
||||
<h4 className="GenKeystore-field-label">Private Key</h4>
|
||||
<TogglablePassword
|
||||
name="privateKey"
|
||||
value={privateKey}
|
||||
disabled={!!privateKey}
|
||||
onChange={this.handleInput}
|
||||
placeholder="f1d0e0789c6d40f39..."
|
||||
isValid={isPrivateKeyValid}
|
||||
/>
|
||||
</label>
|
||||
<label className="GenKeystore-field">
|
||||
<h4 className="GenKeystore-field-label">Password</h4>
|
||||
<TogglablePassword
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={this.handleInput}
|
||||
placeholder={translateRaw('Minimum 9 characters')}
|
||||
isValid={isPasswordValid}
|
||||
/>
|
||||
</label>
|
||||
<div className="input-group-wrapper GenKeystore-field">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<div className="input-group-header">Private Key</div>
|
||||
<TogglablePassword
|
||||
name="privateKey"
|
||||
value={privateKey}
|
||||
disabled={!!privateKey}
|
||||
onChange={this.handleInput}
|
||||
placeholder="f1d0e0789c6d40f39..."
|
||||
isValid={isPrivateKeyValid}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="input-group-wrapper GenKeystore-field">
|
||||
<label className="input-group input-group-inline-dropdown">
|
||||
<div className="input-group-header">Password</div>
|
||||
<TogglablePassword
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={this.handleInput}
|
||||
placeholder={translateRaw('Minimum 9 characters')}
|
||||
isValid={isPasswordValid}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{!keystoreFile ? (
|
||||
<button
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Modal, { IButton } from 'components/ui/Modal';
|
||||
import translate from 'translations';
|
||||
import { CustomNetworkConfig } from 'types/network';
|
||||
|
@ -13,13 +12,17 @@ import {
|
|||
getStaticNetworkConfigs
|
||||
} from 'selectors/config';
|
||||
import { CustomNode } from 'libs/nodes';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
const CUSTOM = 'custom';
|
||||
|
||||
interface Input {
|
||||
interface InputProps {
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
autoComplete?: 'off';
|
||||
onFocus?(): void;
|
||||
onBlur?(): void;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
|
@ -40,7 +43,6 @@ interface StateProps {
|
|||
interface State {
|
||||
name: string;
|
||||
url: string;
|
||||
port: string;
|
||||
network: string;
|
||||
customNetworkId: string;
|
||||
customNetworkUnit: string;
|
||||
|
@ -56,7 +58,6 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
public state: State = {
|
||||
name: '',
|
||||
url: '',
|
||||
port: '',
|
||||
network: Object.keys(this.props.staticNetworks)[0],
|
||||
customNetworkId: '',
|
||||
customNetworkUnit: '',
|
||||
|
@ -94,6 +95,7 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
isOpen={true}
|
||||
buttons={buttons}
|
||||
handleClose={handleClose}
|
||||
maxWidth={580}
|
||||
>
|
||||
<div>
|
||||
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
|
||||
|
@ -175,27 +177,14 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-9">
|
||||
<div className="col-sm-12">
|
||||
<label>URL</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'url',
|
||||
placeholder: 'https://127.0.0.1/'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-sm-3">
|
||||
<label>{translate('NODE_Port')}</label>
|
||||
{this.renderInput(
|
||||
{
|
||||
name: 'port',
|
||||
placeholder: '8545',
|
||||
type: 'number'
|
||||
placeholder: 'e.g. https://127.0.0.1:8545/',
|
||||
autoComplete: 'off'
|
||||
},
|
||||
invalids
|
||||
)}
|
||||
|
@ -239,15 +228,13 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private renderInput(input: Input, invalids: { [key: string]: boolean }) {
|
||||
private renderInput(input: InputProps, invalids: { [key: string]: boolean }) {
|
||||
return (
|
||||
<input
|
||||
className={classnames({
|
||||
'form-control': true,
|
||||
'is-invalid': this.state[input.name] && invalids[input.name]
|
||||
})}
|
||||
<Input
|
||||
className={`${this.state[input.name] && invalids[input.name] ? 'invalid' : ''}`}
|
||||
value={this.state[input.name]}
|
||||
onChange={this.handleChange}
|
||||
autoComplete="off"
|
||||
{...input}
|
||||
/>
|
||||
);
|
||||
|
@ -256,7 +243,6 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
private getInvalids(): { [key: string]: boolean } {
|
||||
const {
|
||||
url,
|
||||
port,
|
||||
hasAuth,
|
||||
username,
|
||||
password,
|
||||
|
@ -265,7 +251,7 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
customNetworkUnit,
|
||||
customNetworkChainId
|
||||
} = this.state;
|
||||
const required: (keyof State)[] = ['name', 'url', 'port', 'network'];
|
||||
const required: (keyof State)[] = ['name', 'url', 'network'];
|
||||
const invalids: { [key: string]: boolean } = {};
|
||||
|
||||
// Required fields
|
||||
|
@ -275,17 +261,12 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
}
|
||||
});
|
||||
|
||||
// Somewhat valid URL, not 100% fool-proof
|
||||
if (!/https?\:\/\/\w+/i.test(url)) {
|
||||
// Parse the URL, and make sure what they typed isn't parsed as relative.
|
||||
// Not a perfect regex, just checks for protocol + any char
|
||||
if (!/^https?:\/\/.+/i.test(url)) {
|
||||
invalids.url = true;
|
||||
}
|
||||
|
||||
// Numeric port within range
|
||||
const iport = parseInt(port, 10);
|
||||
if (!iport || iport < 1 || iport > 65535) {
|
||||
invalids.port = true;
|
||||
}
|
||||
|
||||
// If they have auth, make sure it's provided
|
||||
if (hasAuth) {
|
||||
if (!username) {
|
||||
|
@ -331,28 +312,25 @@ class CustomNodeModal extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private makeCustomNodeConfigFromState(): CustomNodeConfig {
|
||||
const { network } = this.state;
|
||||
const { network, url, name, username, password } = this.state;
|
||||
|
||||
const networkId =
|
||||
network === CUSTOM
|
||||
? this.makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
|
||||
: network;
|
||||
|
||||
const port = parseInt(this.state.port, 10);
|
||||
const url = this.state.url.trim();
|
||||
const node: Omit<CustomNodeConfig, 'lib'> = {
|
||||
isCustom: true,
|
||||
service: 'your custom node',
|
||||
id: `${url}:${port}`,
|
||||
name: this.state.name.trim(),
|
||||
id: url,
|
||||
name: name.trim(),
|
||||
url,
|
||||
port,
|
||||
network: networkId,
|
||||
...(this.state.hasAuth
|
||||
? {
|
||||
auth: {
|
||||
username: this.state.username,
|
||||
password: this.state.password
|
||||
username,
|
||||
password
|
||||
}
|
||||
}
|
||||
: {})
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import { gasPriceDefaults, HELP_ARTICLE } from 'config';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { Component } from 'react';
|
||||
import { DropdownShell, HelpLink } from 'components/ui';
|
||||
import './GasPriceDropdown.scss';
|
||||
import { SetGasLimitFieldAction } from 'actions/transaction';
|
||||
import { gasPricetoBase } from 'libs/units';
|
||||
import { AppState } from 'reducers';
|
||||
import { getGasPrice } from 'selectors/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface OwnProps {
|
||||
onChange(payload: SetGasLimitFieldAction['payload']): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class GasPriceDropdown extends Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.updateGasPrice = throttle(this.updateGasPrice, 50);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<DropdownShell
|
||||
color="white"
|
||||
size="smr"
|
||||
ariaLabel={`adjust gas price. current price is ${this.props.gasPrice.raw} gwei`}
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = () => {
|
||||
return (
|
||||
<span>
|
||||
Gas Price<span className="hidden-xs">: {this.props.gasPrice.raw} Gwei</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
return (
|
||||
<div className="GasPrice-dropdown-menu dropdown-menu dropdown-menu-right">
|
||||
<div className="GasPrice-header">
|
||||
<span>Gas Price</span>: {this.props.gasPrice.raw} Gwei
|
||||
<input
|
||||
type="range"
|
||||
value={this.props.gasPrice.raw}
|
||||
min={gasPriceDefaults.minGwei}
|
||||
max={gasPriceDefaults.maxGwei}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
<p className="small col-xs-4 text-left GasPrice-padding-reset">Not So Fast</p>
|
||||
<p className="small col-xs-4 text-center GasPrice-padding-reset">Fast</p>
|
||||
<p className="small col-xs-4 text-right GasPrice-padding-reset">Fast AF</p>
|
||||
<p className="small GasPrice-description">
|
||||
Gas Price is the amount you pay per unit of gas.{' '}
|
||||
<code>TX fee = gas price * gas limit</code> & is paid to miners for including your TX in
|
||||
a block. Higher the gas price = faster transaction, but more expensive. Default is{' '}
|
||||
<code>21 GWEI</code>.
|
||||
</p>
|
||||
<p>
|
||||
<HelpLink article={HELP_ARTICLE.WHAT_IS_GAS}>Read more</HelpLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private updateGasPrice = (value: string) => {
|
||||
this.props.onChange({ raw: value, value: gasPricetoBase(parseInt(value, 10)) });
|
||||
};
|
||||
|
||||
private handleGasPriceChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
this.updateGasPrice(e.currentTarget.value);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState): StateProps => ({ gasPrice: getGasPrice(state) });
|
||||
|
||||
export default connect(mapStateToProps)(GasPriceDropdown);
|
|
@ -41,6 +41,7 @@ $small-size: 900px;
|
|||
font-weight: 300;
|
||||
color: #fff;
|
||||
transition: background-color 80ms ease;
|
||||
background: $gray-darker;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
|
@ -54,7 +55,6 @@ $small-size: 900px;
|
|||
}
|
||||
|
||||
// Colors
|
||||
&,
|
||||
&.is-primary {
|
||||
background: $brand-primary;
|
||||
}
|
||||
|
@ -81,8 +81,7 @@ $small-size: 900px;
|
|||
color: white;
|
||||
padding: 0;
|
||||
background: #274e7e;
|
||||
background-image: url('~assets/images/header-bg.jpg'),
|
||||
linear-gradient(130deg, #37709e, #274e7e);
|
||||
background-image: url('~assets/images/header-bg.jpg'), linear-gradient(130deg, #37709e, #274e7e);
|
||||
background-size: cover;
|
||||
background-position: left;
|
||||
|
||||
|
@ -111,8 +110,7 @@ $small-size: 900px;
|
|||
width: auto;
|
||||
padding: 8px;
|
||||
padding-right: 0;
|
||||
filter: drop-shadow(0 1px 0 rgba(#000, 0.12))
|
||||
drop-shadow(1px 1px 0 rgba(#000, 0.12));
|
||||
filter: drop-shadow(0 1px 0 rgba(#000, 0.12)) drop-shadow(1px 1px 0 rgba(#000, 0.12));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
addCustomNetwork
|
||||
} from 'actions/config';
|
||||
import logo from 'assets/images/logo-mycrypto.svg';
|
||||
import { Dropdown, ColorDropdown } from 'components/ui';
|
||||
import { OldDropDown, ColorDropdown } from 'components/ui';
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
@ -100,26 +100,26 @@ class Header extends Component<Props, State> {
|
|||
} = this.props;
|
||||
const { isAddingCustomNode } = this.state;
|
||||
const selectedLanguage = languageSelection;
|
||||
const LanguageDropDown = Dropdown as new () => Dropdown<typeof selectedLanguage>;
|
||||
const LanguageDropDown = OldDropDown as new () => OldDropDown<typeof selectedLanguage>;
|
||||
const options = nodeOptions.map(n => {
|
||||
if (n.isCustom) {
|
||||
const { name: { networkId, nodeId }, isCustom, id, ...rest } = n;
|
||||
const { label, isCustom, id, ...rest } = n;
|
||||
return {
|
||||
...rest,
|
||||
name: (
|
||||
<span>
|
||||
{networkId} - {nodeId} <small>(custom)</small>
|
||||
{label.network} - {label.nodeName} <small>(custom)</small>
|
||||
</span>
|
||||
),
|
||||
onRemove: () => this.props.removeCustomNode({ id })
|
||||
};
|
||||
} else {
|
||||
const { name: { networkId, service }, isCustom, ...rest } = n;
|
||||
const { label, isCustom, ...rest } = n;
|
||||
return {
|
||||
...rest,
|
||||
name: (
|
||||
<span>
|
||||
{networkId} <small>({service})</small>
|
||||
{label.network} <small>({label.service})</small>
|
||||
</span>
|
||||
)
|
||||
};
|
||||
|
@ -155,13 +155,6 @@ class Header extends Component<Props, State> {
|
|||
ariaLabel={`change language. current language ${languages[selectedLanguage]}`}
|
||||
options={Object.values(languages)}
|
||||
value={languages[selectedLanguage]}
|
||||
extra={
|
||||
<li key="disclaimer">
|
||||
<a data-toggle="modal" data-target="#disclaimerModal">
|
||||
Disclaimer
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
onChange={this.changeLanguage}
|
||||
size="smr"
|
||||
color="white"
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
&-refresh {
|
||||
@include reset-button;
|
||||
height: $input-height-base;
|
||||
opacity: 0.3;
|
||||
transition: opacity 300ms;
|
||||
|
||||
|
@ -31,16 +30,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-spinner {
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
&-spinner,
|
||||
&-refresh {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateZ(0);
|
||||
margin: 0 1rem;
|
||||
bottom: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import translate from 'translations';
|
||||
import { NonceFieldFactory } from 'components/NonceFieldFactory';
|
||||
import Help from 'components/ui/Help';
|
||||
import { Spinner } from 'components/ui';
|
||||
import { Spinner, Input } from 'components/ui';
|
||||
import { connect } from 'react-redux';
|
||||
import { getNonceRequested, TGetNonceRequested } from 'actions/transaction';
|
||||
import { nonceRequestPending } from 'selectors/transaction';
|
||||
|
@ -32,19 +32,17 @@ class NonceField extends React.Component<Props> {
|
|||
<NonceFieldFactory
|
||||
withProps={({ nonce: { raw, value }, onChange, readOnly, shouldDisplay }) => {
|
||||
return alwaysDisplay || shouldDisplay ? (
|
||||
<React.Fragment>
|
||||
<div className="Nonce-label flex-wrapper">
|
||||
<label className="Nonce-label-text">{translate('OFFLINE_Step2_Label_5')}</label>
|
||||
<Help
|
||||
size="x1"
|
||||
link="https://support.mycrypto.com/transactions/what-is-nonce.html"
|
||||
/>
|
||||
</div>
|
||||
<div className="Nonce-field">
|
||||
<input
|
||||
className={`Nonce-field-input form-control ${
|
||||
!!value ? 'is-valid' : 'is-invalid'
|
||||
}`}
|
||||
<div className="input-group-wrapper Nonce-label">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate('OFFLINE_Step2_Label_5')}
|
||||
<Help
|
||||
size="x1"
|
||||
link="https://support.mycrypto.com/transactions/what-is-nonce.html"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
className={`Nonce-field-input ${!!value ? 'is-valid' : 'is-invalid'}`}
|
||||
type="number"
|
||||
placeholder="e.g. 7"
|
||||
value={raw}
|
||||
|
@ -63,8 +61,8 @@ class NonceField extends React.Component<Props> {
|
|||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</label>
|
||||
</div>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { ConfirmationModal } from 'components/ConfirmationModal';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
export interface CallbackProps {
|
||||
onClick(): void;
|
||||
|
@ -42,12 +43,7 @@ class SendButtonFactoryClass extends Component<Props> {
|
|||
? 'Transaction Parameters'
|
||||
: translate('SEND_raw')}
|
||||
</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={getStringifiedTx(serializedTransaction)}
|
||||
rows={4}
|
||||
readOnly={true}
|
||||
/>
|
||||
<TextArea value={getStringifiedTx(serializedTransaction)} rows={4} readOnly={true} />
|
||||
</div>
|
||||
{!onlyTransactionParameters && (
|
||||
<div className="col-sm-6">
|
||||
|
@ -56,12 +52,7 @@ class SendButtonFactoryClass extends Component<Props> {
|
|||
? 'Serialized Transaction Parameters'
|
||||
: translate('SEND_signed')}
|
||||
</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={addHexPrefix(serializedTransaction)}
|
||||
rows={4}
|
||||
readOnly={true}
|
||||
/>
|
||||
<TextArea value={addHexPrefix(serializedTransaction)} rows={4} readOnly={true} />
|
||||
</div>
|
||||
)}
|
||||
<OfflineBroadcast />
|
||||
|
|
|
@ -27,7 +27,7 @@ class SendEverythingClass extends Component<Props> {
|
|||
!readOnly ? (
|
||||
<span className="help-block">
|
||||
<a onClick={this.onSendEverything}>
|
||||
<span className="strong">{translate('SEND_TransferTotal')}</span>
|
||||
<span className="">{translate('SEND_TransferTotal')}</span>
|
||||
</a>
|
||||
</span>
|
||||
) : null
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
transition-duration: 100ms;
|
||||
|
||||
&.is-active {
|
||||
border-bottom-color: $ether-blue;
|
||||
border-bottom-color: $brand-primary;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
|
|
|
@ -76,7 +76,10 @@ class TXMetaDataPanel extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.offline && !nextProps.offline) {
|
||||
if (
|
||||
(this.props.offline && !nextProps.offline) ||
|
||||
this.props.network.unit !== nextProps.network.unit
|
||||
) {
|
||||
this.props.fetchCCRates([this.props.network.unit]);
|
||||
}
|
||||
if (this.props.gasPrice !== nextProps.gasPrice) {
|
||||
|
@ -108,11 +111,9 @@ class TXMetaDataPanel extends React.Component<Props, State> {
|
|||
!disableToggle && (
|
||||
<div className="help-block">
|
||||
<a className="Gas-toggle" onClick={this.toggleAdvanced}>
|
||||
<strong>
|
||||
{showAdvanced
|
||||
? `- ${translateRaw('Back to simple')}`
|
||||
: `+ ${translateRaw('Advanced Settings')}`}
|
||||
</strong>
|
||||
{showAdvanced
|
||||
? `- ${translateRaw('Back to simple')}`
|
||||
: `+ ${translateRaw('Advanced Settings')}`}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
@ -125,8 +126,10 @@ class TXMetaDataPanel extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
private handleGasPriceInput = (raw: string) => {
|
||||
const gasBn = new BN(raw);
|
||||
const value = gasBn.mul(new BN(Units.gwei));
|
||||
// Realistically, we're not going to end up with a > 32 bit int, so it's
|
||||
// safe to cast to float, multiply by gwei units, then big number, since
|
||||
// some of the inputs may be sub-one float values.
|
||||
const value = new BN(parseFloat(raw) * parseFloat(Units.gwei));
|
||||
this.setState({
|
||||
gasPrice: { raw, value }
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import './AdvancedGas.scss';
|
||||
|
@ -11,6 +10,7 @@ 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';
|
||||
|
||||
export interface AdvancedOptions {
|
||||
gasPriceField?: boolean;
|
||||
|
@ -71,17 +71,19 @@ class AdvancedGas extends React.Component<Props, State> {
|
|||
|
||||
<div className="AdvancedGas-flex-wrapper flex-wrapper">
|
||||
{gasPriceField && (
|
||||
<div className="AdvancedGas-gas-price">
|
||||
<label>{translate('OFFLINE_Step2_Label_3')} (gwei)</label>
|
||||
<input
|
||||
className={classnames('form-control', {
|
||||
'is-invalid': !!gasPrice.raw && !validGasPrice
|
||||
})}
|
||||
type="number"
|
||||
placeholder="40"
|
||||
value={gasPrice.raw}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
<div className="input-group-wrapper AdvancedGas-gas-price">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate('OFFLINE_Step2_Label_3')} (gwei)
|
||||
</div>
|
||||
<Input
|
||||
className={!!gasPrice.raw && !validGasPrice ? 'is-invalid' : ''}
|
||||
type="number"
|
||||
placeholder="40"
|
||||
value={gasPrice.raw}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import Slider from 'rc-slider';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { gasPriceDefaults } from 'config';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import './SimpleGas.scss';
|
||||
import { AppState } from 'reducers';
|
||||
|
@ -15,6 +14,7 @@ import { fetchGasEstimates, TFetchGasEstimates } from 'actions/gas';
|
|||
import { getIsWeb3Node } from 'selectors/config';
|
||||
import { getEstimates, getIsEstimating } from 'selectors/gas';
|
||||
import { Wei, fromWei } from 'libs/units';
|
||||
import { gasPriceDefaults } from 'config';
|
||||
import { InlineSpinner } from 'components/ui/InlineSpinner';
|
||||
const SliderWithTooltip = Slider.createSliderWithTooltip(Slider);
|
||||
|
||||
|
@ -41,7 +41,7 @@ type Props = OwnProps & StateProps & ActionProps;
|
|||
|
||||
class SimpleGas extends React.Component<Props> {
|
||||
public componentDidMount() {
|
||||
this.fixGasPrice(this.props.gasPrice);
|
||||
this.fixGasPrice();
|
||||
this.props.fetchGasEstimates();
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,8 @@ class SimpleGas extends React.Component<Props> {
|
|||
} = this.props;
|
||||
|
||||
const bounds = {
|
||||
max: gasEstimates ? gasEstimates.fastest : gasPriceDefaults.minGwei,
|
||||
min: gasEstimates ? gasEstimates.safeLow : gasPriceDefaults.maxGwei
|
||||
max: gasEstimates ? gasEstimates.fastest : gasPriceDefaults.max,
|
||||
min: gasEstimates ? gasEstimates.safeLow : gasPriceDefaults.min
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -93,6 +93,7 @@ class SimpleGas extends React.Component<Props> {
|
|||
onChange={this.handleSlider}
|
||||
min={bounds.min}
|
||||
max={bounds.max}
|
||||
step={bounds.min < 1 ? 0.1 : 1}
|
||||
value={this.getGasPriceGwei(gasPrice.value)}
|
||||
tipFormatter={this.formatTooltip}
|
||||
disabled={isGasEstimating}
|
||||
|
@ -119,13 +120,18 @@ class SimpleGas extends React.Component<Props> {
|
|||
this.props.inputGasPrice(gasGwei.toString());
|
||||
};
|
||||
|
||||
private fixGasPrice(gasPrice: AppState['transaction']['fields']['gasPrice']) {
|
||||
private fixGasPrice() {
|
||||
const { gasPrice, gasEstimates } = this.props;
|
||||
if (!gasEstimates) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the gas price is above or below our minimum, bring it in line
|
||||
const gasPriceGwei = this.getGasPriceGwei(gasPrice.value);
|
||||
if (gasPriceGwei > gasPriceDefaults.maxGwei) {
|
||||
this.props.setGasPrice(gasPriceDefaults.maxGwei.toString());
|
||||
} else if (gasPriceGwei < gasPriceDefaults.minGwei) {
|
||||
this.props.setGasPrice(gasPriceDefaults.minGwei.toString());
|
||||
if (gasPriceGwei < gasEstimates.safeLow) {
|
||||
this.props.setGasPrice(gasEstimates.safeLow.toString());
|
||||
} else if (gasPriceGwei > gasEstimates.fastest) {
|
||||
this.props.setGasPrice(gasEstimates.fastest.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
// yourself, otherwise all visibiility changes are managed in internal state.
|
||||
import React from 'react';
|
||||
import './TogglablePassword.scss';
|
||||
import { Input, TextArea } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
// Shared props
|
||||
className?: string;
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
name?: string;
|
||||
|
@ -46,6 +48,7 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
|
||||
public render() {
|
||||
const {
|
||||
className,
|
||||
value,
|
||||
placeholder,
|
||||
name,
|
||||
|
@ -66,10 +69,10 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
: isValid === null || isValid === undefined ? '' : isValid ? 'is-valid' : 'is-invalid';
|
||||
|
||||
return (
|
||||
<div className="TogglablePassword input-group">
|
||||
<div className={`TogglablePassword input-group input-group-inline-dropdown ${className}`}>
|
||||
{isTextareaWhenVisible && isVisible ? (
|
||||
<textarea
|
||||
className={`form-control ${validClass}`}
|
||||
<TextArea
|
||||
className={validClass}
|
||||
value={value}
|
||||
name={name}
|
||||
disabled={disabled}
|
||||
|
@ -82,12 +85,12 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
|
|||
aria-label={ariaLabel}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
<Input
|
||||
value={value}
|
||||
name={name}
|
||||
disabled={disabled}
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
className={`form-control ${validClass}`}
|
||||
className={`${validClass}`}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { Identicon, UnitDisplay, NewTabLink } from 'components/ui';
|
||||
import { Identicon, UnitDisplay, NewTabLink, TextArea, Address } from 'components/ui';
|
||||
import { TransactionData, TransactionReceipt } from 'libs/nodes';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import './TransactionDataTable.scss';
|
||||
|
@ -98,7 +98,7 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
|
|||
data: (
|
||||
<MaybeLink href={explorer.from}>
|
||||
<Identicon address={data.from} size="26px" />
|
||||
{data.from}
|
||||
<Address address={data.from} />
|
||||
</MaybeLink>
|
||||
)
|
||||
},
|
||||
|
@ -107,7 +107,7 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
|
|||
data: (
|
||||
<MaybeLink href={explorer.to}>
|
||||
<Identicon address={data.to} size="26px" />
|
||||
{data.to}
|
||||
<Address address={data.to} />
|
||||
</MaybeLink>
|
||||
)
|
||||
},
|
||||
|
@ -141,7 +141,9 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
|
|||
label: translate('New contract address'),
|
||||
data: receipt &&
|
||||
receipt.contractAddress && (
|
||||
<MaybeLink href={explorer.contract}>{receipt.contractAddress}</MaybeLink>
|
||||
<MaybeLink href={explorer.contract}>
|
||||
<Address address={receipt.contractAddress} />
|
||||
</MaybeLink>
|
||||
)
|
||||
},
|
||||
{
|
||||
|
@ -150,9 +152,7 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
|
|||
},
|
||||
{
|
||||
label: translate('TRANS_data'),
|
||||
data: hasInputData ? (
|
||||
<textarea className="form-control" value={data.input} disabled={true} />
|
||||
) : null
|
||||
data: hasInputData ? <TextArea value={data.input} disabled={true} /> : null
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import React, { Component } from 'react';
|
||||
import { setUnitMeta, TSetUnitMeta } from 'actions/transaction';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
import { withConditional } from 'components/hocs';
|
||||
import { TokenBalance, MergedToken, getShownTokenBalances, getTokens } from 'selectors/wallet';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { getUnit } from 'selectors/transaction';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import { getNetworkUnit } from 'selectors/config';
|
||||
|
||||
interface DispatchProps {
|
||||
setUnitMeta: TSetUnitMeta;
|
||||
|
@ -19,37 +17,32 @@ interface StateProps {
|
|||
tokens: TokenBalance[];
|
||||
allTokens: MergedToken[];
|
||||
showAllTokens?: boolean;
|
||||
network: NetworkConfig;
|
||||
networkUnit: string;
|
||||
}
|
||||
|
||||
const StringDropdown = Dropdown as new () => Dropdown<string>;
|
||||
const ConditionalStringDropDown = withConditional(StringDropdown);
|
||||
|
||||
class UnitDropdownClass extends Component<DispatchProps & StateProps> {
|
||||
public render() {
|
||||
const { tokens, allTokens, showAllTokens, unit, network } = this.props;
|
||||
const { tokens, allTokens, showAllTokens, unit, networkUnit } = this.props;
|
||||
const focusedTokens = showAllTokens ? allTokens : tokens;
|
||||
const options = [networkUnit, ...getTokenSymbols(focusedTokens)];
|
||||
return (
|
||||
<div className="input-group-btn">
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) => (
|
||||
<ConditionalStringDropDown
|
||||
options={[network.unit, ...getTokenSymbols(focusedTokens)]}
|
||||
value={unit === 'ether' ? network.unit : unit}
|
||||
condition={!readOnly}
|
||||
conditionalProps={{
|
||||
onChange: this.handleOnChange
|
||||
}}
|
||||
ariaLabel={'dropdown'}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) => (
|
||||
<Dropdown
|
||||
options={options}
|
||||
value={unit === 'ether' ? networkUnit : unit}
|
||||
onChange={this.handleOnChange}
|
||||
clearable={false}
|
||||
searchable={options.length > 10}
|
||||
disabled={!!readOnly}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
private handleOnChange = (unit: string) => {
|
||||
this.props.setUnitMeta(unit);
|
||||
private handleOnChange = unit => {
|
||||
this.props.setUnitMeta(unit.value);
|
||||
};
|
||||
}
|
||||
const getTokenSymbols = (tokens: (TokenBalance | MergedToken)[]) => tokens.map(t => t.symbol);
|
||||
|
@ -59,7 +52,7 @@ function mapStateToProps(state: AppState) {
|
|||
tokens: getShownTokenBalances(state, true),
|
||||
allTokens: getTokens(state),
|
||||
unit: getUnit(state),
|
||||
network: getNetworkConfig(state)
|
||||
networkUnit: getNetworkUnit(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { getTokens, MergedToken } from 'selectors/wallet';
|
||||
import { UnitDisplay } from 'components/ui';
|
||||
import { UnitDisplay, Input } from 'components/ui';
|
||||
import './DeterministicWalletsModal.scss';
|
||||
import { StaticNetworkConfig } from 'types/network';
|
||||
import Select from 'react-select';
|
||||
|
@ -97,7 +97,6 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
walletType
|
||||
} = this.props;
|
||||
const { selectedAddress, customPath, page } = this.state;
|
||||
const validPathClass = isValidPath(customPath) ? 'is-valid' : 'is-invalid';
|
||||
|
||||
const buttons: IButton[] = [
|
||||
{
|
||||
|
@ -137,8 +136,8 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
/>
|
||||
{/* TODO/Hack - Custom Paths are temporarily disabled. `false` is used for smallest diff */}
|
||||
{false && (
|
||||
<input
|
||||
className={`form-control ${validPathClass}`}
|
||||
<Input
|
||||
className={isValidPath(customPath) ? '' : 'invalid'}
|
||||
value={customPath}
|
||||
placeholder="m/44'/60'/0'/0"
|
||||
onChange={this.handleChangeCustomPath}
|
||||
|
|
|
@ -3,6 +3,7 @@ import React, { PureComponent } from 'react';
|
|||
import translate, { translateRaw } from 'translations';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { TShowNotification } from 'actions/notifications';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export interface KeystoreValue {
|
||||
file: string;
|
||||
|
@ -36,7 +37,7 @@ export class KeystoreDecrypt extends PureComponent {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { isWalletPending, isPasswordPending, value: { file, password } } = this.props;
|
||||
const { isWalletPending, value: { file, password } } = this.props;
|
||||
const passReq = isPassRequired(file);
|
||||
const unlockDisabled = !file || (passReq && !password);
|
||||
|
||||
|
@ -44,7 +45,7 @@ export class KeystoreDecrypt extends PureComponent {
|
|||
<form id="selectedUploadKey" onSubmit={this.unlock}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
className={'hidden'}
|
||||
className="hidden"
|
||||
type="file"
|
||||
id="fselector"
|
||||
onChange={this.handleFileSelection}
|
||||
|
@ -55,17 +56,16 @@ export class KeystoreDecrypt extends PureComponent {
|
|||
</a>
|
||||
</label>
|
||||
{isWalletPending ? <Spinner /> : ''}
|
||||
<div className={file.length && isPasswordPending ? '' : 'hidden'}>
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
className={`${password.length > 0 ? 'is-valid' : 'is-invalid'} ${
|
||||
file.length && isWalletPending ? 'hidden' : ''
|
||||
}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-primary btn-block" disabled={unlockDisabled}>
|
||||
|
|
|
@ -46,6 +46,12 @@ class LedgerNanoSDecryptClass extends PureComponent<Props, State> {
|
|||
});
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath.value });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { dPath, publicKey, chainCode, error, isLoading, showTip } = this.state;
|
||||
const showErr = error ? 'is-showing' : '';
|
||||
|
|
|
@ -8,8 +8,9 @@ import { AppState } from 'reducers';
|
|||
import { connect } from 'react-redux';
|
||||
import { getSingleDPath, getPaths } from 'selectors/config/wallet';
|
||||
import { TogglablePassword } from 'components';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
interface OwnProps {
|
||||
onUnlock(param: any): void;
|
||||
}
|
||||
|
||||
|
@ -18,6 +19,8 @@ interface StateProps {
|
|||
dPaths: DPath[];
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
interface State {
|
||||
phrase: string;
|
||||
formattedPhrase: string;
|
||||
|
@ -26,7 +29,7 @@ interface State {
|
|||
dPath: string;
|
||||
}
|
||||
|
||||
class MnemonicDecryptClass extends PureComponent<Props & StateProps, State> {
|
||||
class MnemonicDecryptClass extends PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
phrase: '',
|
||||
formattedPhrase: '',
|
||||
|
@ -35,6 +38,12 @@ class MnemonicDecryptClass extends PureComponent<Props & StateProps, State> {
|
|||
dPath: this.props.dPath.value
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath.value });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { phrase, formattedPhrase, seed, dPath, pass } = this.state;
|
||||
const isValidMnemonic = validateMnemonic(formattedPhrase);
|
||||
|
@ -55,8 +64,7 @@ class MnemonicDecryptClass extends PureComponent<Props & StateProps, State> {
|
|||
</div>
|
||||
<div className="form-group">
|
||||
<p>Password (optional):</p>
|
||||
<input
|
||||
className="form-control"
|
||||
<Input
|
||||
value={pass}
|
||||
onChange={this.onPasswordChange}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { stripHexPrefix } from 'libs/values';
|
|||
import React, { PureComponent } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { TogglablePassword } from 'components';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export interface PrivateKeyValue {
|
||||
key: string;
|
||||
|
@ -53,29 +54,32 @@ export class PrivateKeyDecrypt extends PureComponent<Props> {
|
|||
|
||||
return (
|
||||
<form id="selectedTypeKey" onSubmit={this.unlock}>
|
||||
<div className="form-group">
|
||||
<TogglablePassword
|
||||
value={key}
|
||||
rows={4}
|
||||
placeholder={translateRaw('x_PrivKey2')}
|
||||
isValid={isValidPkey}
|
||||
isTextareaWhenVisible={true}
|
||||
onChange={this.onPkeyChange}
|
||||
onEnter={this.props.onUnlock}
|
||||
/>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<TogglablePassword
|
||||
value={key}
|
||||
rows={4}
|
||||
placeholder={translateRaw('x_PrivKey2')}
|
||||
isValid={isValidPkey}
|
||||
onChange={this.onPkeyChange}
|
||||
onEnter={this.props.onUnlock}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{isValidPkey &&
|
||||
isPassRequired && (
|
||||
<div className="form-group">
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
<div className="input-group-wrapper">
|
||||
<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'}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={translateRaw('x_Password')}
|
||||
type="password"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<button className="btn btn-block btn-primary" disabled={unlockDisabled}>
|
||||
|
|
|
@ -40,6 +40,12 @@ class TrezorDecryptClass extends PureComponent<Props, State> {
|
|||
isLoading: false
|
||||
};
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.dPath !== nextProps.dPath) {
|
||||
this.setState({ dPath: nextProps.dPath.value });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
|
||||
const showErr = error ? 'is-showing' : '';
|
||||
|
|
|
@ -3,6 +3,7 @@ import translate from 'translations';
|
|||
import { donationAddressMap } from 'config';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { AddressOnlyWallet } from 'libs/wallet';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
onUnlock(param: any): void;
|
||||
|
@ -24,10 +25,8 @@ export class ViewOnlyDecrypt extends PureComponent<Props, State> {
|
|||
return (
|
||||
<div id="selectedUploadKey">
|
||||
<form className="form-group" onSubmit={this.openWallet}>
|
||||
<textarea
|
||||
className={`form-control
|
||||
${isValid ? 'is-valid' : 'is-invalid'}
|
||||
`}
|
||||
<TextArea
|
||||
className={isValid ? 'is-valid' : 'is-invalid'}
|
||||
value={address}
|
||||
onChange={this.changeAddress}
|
||||
onKeyDown={this.handleEnterKey}
|
||||
|
|
|
@ -13,7 +13,7 @@ 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 AlphaAgreement } from './AlphaAgreement';
|
||||
export { default as BetaAgreement } from './BetaAgreement';
|
||||
export { default as TXMetaDataPanel } from './TXMetaDataPanel';
|
||||
export { default as WalletDecrypt } from './WalletDecrypt';
|
||||
export { default as TogglablePassword } from './TogglablePassword';
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import NewTabLink from './NewTabLink';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { BlockExplorerConfig } from 'types/network';
|
||||
|
||||
interface BaseProps {
|
||||
explorer?: BlockExplorerConfig | null;
|
||||
}
|
||||
|
||||
interface AddressProps extends BaseProps {
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface WalletProps extends BaseProps {
|
||||
wallet: IWallet;
|
||||
}
|
||||
|
||||
type Props = AddressProps | WalletProps;
|
||||
|
||||
const isAddressProps = (props: Props): props is AddressProps =>
|
||||
typeof (props as AddressProps).address === 'string';
|
||||
|
||||
const Address: React.SFC<Props> = props => {
|
||||
let addr = '';
|
||||
if (isAddressProps(props)) {
|
||||
addr = props.address;
|
||||
} else {
|
||||
addr = props.wallet.getAddressString();
|
||||
}
|
||||
addr = toChecksumAddress(addr);
|
||||
|
||||
if (props.explorer) {
|
||||
return <NewTabLink href={props.explorer.addressUrl(addr)}>{addr}</NewTabLink>;
|
||||
} else {
|
||||
return <React.Fragment>{addr}</React.Fragment>;
|
||||
}
|
||||
};
|
||||
|
||||
export default Address;
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { withConditional } from 'components/hocs';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
const Input: React.SFC<React.InputHTMLAttributes<any>> = props => <input {...props} />;
|
||||
export const ConditionalInput = withConditional(Input);
|
||||
const inpt: React.SFC<React.InputHTMLAttributes<any>> = props => <Input {...props} />;
|
||||
export const ConditionalInput = withConditional(inpt);
|
||||
|
|
|
@ -1,123 +1,63 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import DropdownShell from './DropdownShell';
|
||||
import React from 'react';
|
||||
import Select, { ReactSelectProps } from 'react-select';
|
||||
|
||||
interface Props<T> {
|
||||
value: T | undefined;
|
||||
options: T[];
|
||||
ariaLabel: string;
|
||||
label?: string;
|
||||
extra?: any;
|
||||
size?: string;
|
||||
color?: string;
|
||||
menuAlign?: string;
|
||||
formatTitle?(option: T): any;
|
||||
onChange?(value: T): void;
|
||||
interface Props extends ReactSelectProps {
|
||||
className?: string;
|
||||
options: any;
|
||||
onChange: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
search: string;
|
||||
}
|
||||
|
||||
export default class DropdownComponent<T> extends PureComponent<Props<T>, State> {
|
||||
export default class Dropdown extends React.Component<Props> {
|
||||
public state = {
|
||||
search: ''
|
||||
selectedOption: { value: '', label: '' },
|
||||
hasBlurred: false
|
||||
};
|
||||
|
||||
private dropdownShell: DropdownShell | null;
|
||||
public handleChange = selectedOption => {
|
||||
this.setState({ selectedOption });
|
||||
};
|
||||
|
||||
public formatOptions = options => {
|
||||
if (typeof options[0] === 'object') {
|
||||
return options;
|
||||
}
|
||||
const formatted = options.map(opt => {
|
||||
return { value: opt, label: opt };
|
||||
});
|
||||
return formatted;
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { ariaLabel, color, size } = this.props;
|
||||
const { onChange } = this.props;
|
||||
const { selectedOption } = this.state;
|
||||
const value = selectedOption && selectedOption.value;
|
||||
const options = this.formatOptions(this.props.options);
|
||||
|
||||
return (
|
||||
<DropdownShell
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
size={size}
|
||||
color={color}
|
||||
ariaLabel={ariaLabel}
|
||||
ref={el => (this.dropdownShell = el)}
|
||||
<Select
|
||||
// use ref to prevent <label /> from stealing focus when used inline with an input
|
||||
ref={el => {
|
||||
if (!!el && !!(el as any).control) {
|
||||
(el as any).control.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}}
|
||||
className={`${this.props.className} ${this.state.hasBlurred ? 'has-blurred' : ''}`}
|
||||
value={value}
|
||||
onChange={obj => {
|
||||
this.handleChange(obj);
|
||||
onChange();
|
||||
}}
|
||||
{...this.props}
|
||||
onBlur={e => {
|
||||
this.setState({ hasBlurred: true });
|
||||
if (this.props && this.props.onBlur) {
|
||||
this.props.onBlur(e);
|
||||
}
|
||||
}}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = () => {
|
||||
const { value } = this.props;
|
||||
const labelStr = this.props.label ? `${this.props.label}:` : '';
|
||||
return (
|
||||
<span>
|
||||
{labelStr} {this.formatTitle(value)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
const { options, value, menuAlign, extra } = this.props;
|
||||
const { search } = this.state;
|
||||
const searchable = options.length > 20;
|
||||
const menuClass = classnames({
|
||||
'dropdown-menu': true,
|
||||
[`dropdown-menu-${menuAlign || ''}`]: !!menuAlign
|
||||
});
|
||||
const searchableStyle = {
|
||||
maxHeight: '300px',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
const searchRegex = new RegExp(search, 'gi');
|
||||
const onSearchChange = e => {
|
||||
this.setState({ search: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className={menuClass} style={searchable ? searchableStyle : {}}>
|
||||
{searchable && (
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder={'Search'}
|
||||
onChange={onSearchChange}
|
||||
value={search}
|
||||
/>
|
||||
)}
|
||||
|
||||
{options
|
||||
.filter(option => {
|
||||
if (searchable && search.length) {
|
||||
return option.toString().match(searchRegex);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((option, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
className={option === value ? 'active' : ''}
|
||||
onClick={this.onChange.bind(null, option)}
|
||||
>
|
||||
{this.props.formatTitle ? this.formatTitle(option) : option}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{extra && <li key={'separator'} role="separator" className="divider" />}
|
||||
{extra}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
private formatTitle = (option: any) => {
|
||||
if (this.props.formatTitle) {
|
||||
return this.props.formatTitle(option);
|
||||
} else {
|
||||
return option;
|
||||
}
|
||||
};
|
||||
|
||||
private onChange = (value: any) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
if (this.dropdownShell) {
|
||||
this.dropdownShell.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
.Help {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
padding: 4px;
|
||||
box-sizing: content-box;
|
||||
box-sizing: border-box;
|
||||
line-height: inherit;
|
||||
vertical-align: top;
|
||||
transition: opacity 0.3s;
|
||||
|
@ -14,17 +13,17 @@
|
|||
}
|
||||
|
||||
&-x1 {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
&-x2 {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
&-x3 {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.example-form {
|
||||
max-width: 744px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .TogglablePassword {
|
||||
width: 100%;
|
||||
}
|
||||
&-header {
|
||||
display: flex;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 400;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
> *:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
> .flex-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
> .small {
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
}
|
||||
&-input {
|
||||
width: 100%;
|
||||
border: 1px solid #e5ecf3;
|
||||
border-radius: 2px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
box-shadow: inset 0 1px 0 0 rgba(63, 63, 68, 0.05);
|
||||
transition: border-color 120ms, box-shadow 120ms;
|
||||
margin-bottom: 1rem;
|
||||
&::placeholder {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
&:not([disabled]):not([readonly]) {
|
||||
&.invalid.has-blurred.has-value {
|
||||
border-color: $brand-danger;
|
||||
box-shadow: inset 0px 0px 0px 1px $brand-danger;
|
||||
}
|
||||
&:focus {
|
||||
border-color: #4295bc;
|
||||
box-shadow: inset 0px 0px 0px 1px #4295bc;
|
||||
&.valid {
|
||||
border-color: #8dd17b;
|
||||
box-shadow: inset 0px 0px 0px 1px #8dd17b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-inline-dropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 1rem;
|
||||
flex-wrap: wrap;
|
||||
> .input-group-header {
|
||||
width: 100%;
|
||||
}
|
||||
> .input-group-input {
|
||||
flex-grow: 1;
|
||||
width: auto;
|
||||
}
|
||||
> .Select {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.Swap-dropdown {
|
||||
.Select-input {
|
||||
left: 24px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import React, { HTMLProps } from 'react';
|
||||
import './Input.scss';
|
||||
|
||||
interface State {
|
||||
hasBlurred: boolean;
|
||||
}
|
||||
|
||||
class Input extends React.Component<HTMLProps<HTMLInputElement>, State> {
|
||||
public state: State = {
|
||||
hasBlurred: false
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
<input
|
||||
{...this.props}
|
||||
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' : ''
|
||||
} ${!!this.props.value && this.props.value.toString().length > 0 ? 'has-value' : ''}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Input;
|
|
@ -111,7 +111,7 @@ $m-anim-speed: 400ms;
|
|||
|
||||
// Mobile styles
|
||||
@media(max-width: $screen-sm) {
|
||||
width: calc(100% - 40px);
|
||||
width: calc(100% - 40px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,13 @@ interface Props {
|
|||
disableButtons?: boolean;
|
||||
children: any;
|
||||
buttons?: IButton[];
|
||||
maxWidth?: number;
|
||||
handleClose?(): void;
|
||||
}
|
||||
interface ModalStyle {
|
||||
width?: string;
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
const Fade = ({ children, ...props }) => (
|
||||
<CSSTransition {...props} timeout={300} classNames="animate-modal">
|
||||
|
@ -46,16 +51,22 @@ export default class Modal extends PureComponent<Props, {}> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { isOpen, title, children, buttons, handleClose } = this.props;
|
||||
const { isOpen, title, children, buttons, handleClose, maxWidth } = this.props;
|
||||
const hasButtons = buttons && buttons.length;
|
||||
const modalStyle: ModalStyle = {};
|
||||
|
||||
if (maxWidth) {
|
||||
modalStyle.width = '100%';
|
||||
modalStyle.maxWidth = `${maxWidth}px`;
|
||||
}
|
||||
|
||||
return (
|
||||
<TransitionGroup>
|
||||
{isOpen && (
|
||||
<Fade>
|
||||
<div>
|
||||
<div className={`Modalshade`} />
|
||||
<div className={`Modal`}>
|
||||
<div className="Modalshade" />
|
||||
<div className="Modal" style={modalStyle}>
|
||||
{title && (
|
||||
<div className="Modal-header flex-wrapper">
|
||||
<h2 className="Modal-header-title">{title}</h2>
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import DropdownShell from './DropdownShell';
|
||||
|
||||
interface Props<T> {
|
||||
value: T | undefined;
|
||||
options: T[];
|
||||
ariaLabel: string;
|
||||
label?: string;
|
||||
extra?: any;
|
||||
size?: string;
|
||||
color?: string;
|
||||
menuAlign?: string;
|
||||
formatTitle?(option: T): any;
|
||||
onChange?(value: T): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
search: string;
|
||||
}
|
||||
|
||||
export default class DropdownComponent<T> extends PureComponent<Props<T>, State> {
|
||||
public state = {
|
||||
search: ''
|
||||
};
|
||||
|
||||
private dropdownShell: DropdownShell | null;
|
||||
|
||||
public render() {
|
||||
const { ariaLabel, color, size } = this.props;
|
||||
|
||||
return (
|
||||
<DropdownShell
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
size={size}
|
||||
color={color}
|
||||
ariaLabel={ariaLabel}
|
||||
ref={el => (this.dropdownShell = el)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = () => {
|
||||
const { value } = this.props;
|
||||
const labelStr = this.props.label ? `${this.props.label}:` : '';
|
||||
return (
|
||||
<span>
|
||||
{labelStr} {this.formatTitle(value)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
const { options, value, menuAlign, extra } = this.props;
|
||||
const { search } = this.state;
|
||||
const searchable = options.length > 20;
|
||||
const menuClass = classnames({
|
||||
'dropdown-menu': true,
|
||||
[`dropdown-menu-${menuAlign || ''}`]: !!menuAlign
|
||||
});
|
||||
const searchableStyle = {
|
||||
maxHeight: '300px',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
const searchRegex = new RegExp(search, 'gi');
|
||||
const onSearchChange = e => {
|
||||
this.setState({ search: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className={menuClass} style={searchable ? searchableStyle : {}}>
|
||||
{searchable && (
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder={'Search'}
|
||||
onChange={onSearchChange}
|
||||
value={search}
|
||||
/>
|
||||
)}
|
||||
|
||||
{options
|
||||
.filter(option => {
|
||||
if (searchable && search.length) {
|
||||
return option.toString().match(searchRegex);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((option, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
className={option === value ? 'active' : ''}
|
||||
onClick={this.onChange.bind(null, option)}
|
||||
>
|
||||
{this.props.formatTitle ? this.formatTitle(option) : option}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{extra && <li key={'separator'} role="separator" className="divider" />}
|
||||
{extra}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
private formatTitle = (option: any) => {
|
||||
if (this.props.formatTitle) {
|
||||
return this.props.formatTitle(option);
|
||||
} else {
|
||||
return option;
|
||||
}
|
||||
};
|
||||
|
||||
private onChange = (value: any) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
if (this.dropdownShell) {
|
||||
this.dropdownShell.close();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.SimpleButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .Spinner {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import Spinner from './Spinner';
|
||||
import './SimpleButton.scss';
|
||||
|
||||
const DEFAULT_BUTTON_TYPE = 'primary';
|
||||
const DEFAULT_BUTTON_SIZE = 'lg';
|
||||
|
@ -29,8 +30,8 @@ export default class SimpleButton extends Component<Props, {}> {
|
|||
<div>
|
||||
<button onClick={onClick} disabled={loading || disabled} className={this.computedClass()}>
|
||||
{loading ? (
|
||||
<div>
|
||||
<Spinner /> {loadingText || text}
|
||||
<div className="SimpleButton">
|
||||
<Spinner light={true} /> {loadingText || text}
|
||||
</div>
|
||||
) : (
|
||||
<div>{text}</div>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import Dropdown from './Dropdown';
|
||||
|
||||
interface Props {
|
||||
value: string | undefined;
|
||||
options: string[];
|
||||
ariaLabel?: string;
|
||||
onChange(value: string): void;
|
||||
}
|
||||
|
||||
export default class SimpleDropdown extends PureComponent<Props, void> {
|
||||
public render() {
|
||||
const { options, value, onChange, ariaLabel } = this.props;
|
||||
|
||||
const StringDropdown = Dropdown as new () => Dropdown<string>;
|
||||
|
||||
return (
|
||||
<StringDropdown
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ariaLabel={ariaLabel || 'dropdown'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -112,3 +112,16 @@
|
|||
padding-right: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.swap-option {
|
||||
&-wrapper {
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
&-img {
|
||||
width: 1rem;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import './SwapDropdown.scss';
|
||||
import classnames from 'classnames';
|
||||
import { DropDown } from 'components/ui';
|
||||
|
||||
export interface SingleCoin {
|
||||
id: string;
|
||||
|
@ -10,91 +10,70 @@ export interface SingleCoin {
|
|||
}
|
||||
|
||||
interface Props<T> {
|
||||
ariaLabel: string;
|
||||
options: SingleCoin[];
|
||||
value: string;
|
||||
onChange(value: T): void;
|
||||
}
|
||||
|
||||
class SwapDropdown<T> extends PureComponent<Props<T>, {}> {
|
||||
public state = {
|
||||
open: false
|
||||
const ValueComp: React.SFC = (props: any) => {
|
||||
return (
|
||||
<div className={`${props.className} swap-option-wrapper`}>
|
||||
<img src={props.value.img} className="swap-option-img" />
|
||||
<span className="swap-option-label">{props.value.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OptionComp: React.SFC = (props: any) => {
|
||||
const handleMouseDown = event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
props.onSelect(props.option, event);
|
||||
};
|
||||
|
||||
private dropdown: HTMLElement | null;
|
||||
|
||||
public componentDidMount() {
|
||||
document.addEventListener('click', this.clickHandler);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('click', this.clickHandler);
|
||||
}
|
||||
|
||||
public handleClickOutside() {
|
||||
this.toggleDropdown();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { open } = this.state;
|
||||
const { options, value } = this.props;
|
||||
const dropdownGrid = classnames(open && 'open', 'SwapDropdown-grid');
|
||||
|
||||
const mappedCoins = options.sort((a, b) => (a.id > b.id ? 1 : -1)).map((coin: SingleCoin) => {
|
||||
const cn = classnames(coin.status !== 'available' && 'inactive', 'SwapDropdown-item');
|
||||
return (
|
||||
<li className={cn} key={coin.id}>
|
||||
<a onClick={coin.status === 'available' ? this.onChange.bind(null, coin.id) : null}>
|
||||
<img src={coin.image} height="20" width="20" />
|
||||
{/* <div className="SwapDropdown-desc"> */}
|
||||
<strong>{coin.id}</strong>
|
||||
<br />
|
||||
<small>{coin.name}</small>
|
||||
{/* </div> */}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className="SwapDropdown" ref={el => (this.dropdown = el)}>
|
||||
<button onClick={this.toggleDropdown}>
|
||||
{value}
|
||||
<i className="caret" />
|
||||
</button>
|
||||
<ul className={dropdownGrid}>{mappedCoins}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
private toggleDropdown = () => {
|
||||
this.setState({
|
||||
open: !this.state.open
|
||||
});
|
||||
const handleMouseEnter = event => {
|
||||
props.onFocus(props.option, event);
|
||||
};
|
||||
|
||||
private onChange = (value: any) => {
|
||||
this.props.onChange(value);
|
||||
if (this.state.open) {
|
||||
this.setState({
|
||||
open: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private clickHandler = (ev: Event) => {
|
||||
if (!this.state.open || !this.dropdown) {
|
||||
const handleMouseMove = event => {
|
||||
if (props.isFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.dropdown !== ev.target &&
|
||||
ev.target instanceof HTMLElement &&
|
||||
!this.dropdown.contains(ev.target)
|
||||
) {
|
||||
this.setState({
|
||||
open: false
|
||||
});
|
||||
}
|
||||
props.onFocus(props.option, event);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`${props.className} swap-option-wrapper`}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<img src={props.option.img} className="swap-option-img" />
|
||||
<span className="swap-option-label">{props.option.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class SwapDropdown<T> extends PureComponent<Props<T>> {
|
||||
public render() {
|
||||
const { options, value, onChange } = this.props;
|
||||
const mappedOptions = options.map(opt => {
|
||||
return { label: opt.id, value: opt.name, img: opt.image, status: opt.status };
|
||||
});
|
||||
return (
|
||||
<DropDown
|
||||
className="Swap-dropdown"
|
||||
options={mappedOptions}
|
||||
optionComponent={(props: any) => {
|
||||
return <OptionComp {...props} />;
|
||||
}}
|
||||
value={value}
|
||||
clearable={false}
|
||||
onChange={onChange}
|
||||
valueComponent={(props: any) => {
|
||||
return <ValueComp {...props} />;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SwapDropdown;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import React, { HTMLProps } from 'react';
|
||||
import './Input.scss';
|
||||
|
||||
interface State {
|
||||
hasBlurred: boolean;
|
||||
}
|
||||
|
||||
class TextArea extends React.Component<HTMLProps<HTMLTextAreaElement>, State> {
|
||||
public state: State = {
|
||||
hasBlurred: false
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
<textarea
|
||||
{...this.props}
|
||||
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' : ''
|
||||
}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TextArea;
|
|
@ -1,5 +1,6 @@
|
|||
export { default as ColorDropdown } from './ColorDropdown';
|
||||
export { default as Dropdown } from './Dropdown';
|
||||
export { default as OldDropDown } from './OldDropdown';
|
||||
export { default as DropDown } from './Dropdown';
|
||||
export { default as DropdownShell } from './DropdownShell';
|
||||
export { default as Identicon } from './Identicon';
|
||||
export { default as Modal } from './Modal';
|
||||
|
@ -12,6 +13,9 @@ 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 * from './ConditionalInput';
|
||||
export * from './Expandable';
|
||||
export * from './InlineSpinner';
|
||||
|
|
|
@ -3,6 +3,6 @@ export const GAS_LIMIT_LOWER_BOUND = 21000;
|
|||
export const GAS_LIMIT_UPPER_BOUND = 8000000;
|
||||
|
||||
// Lower/upper ranges for gas price in gwei
|
||||
export const GAS_PRICE_GWEI_LOWER_BOUND = 1;
|
||||
export const GAS_PRICE_GWEI_UPPER_BOUND = 10000;
|
||||
export const GAS_PRICE_GWEI_DEFAULT = 40;
|
||||
export const GAS_PRICE_GWEI_LOWER_BOUND = 0.01;
|
||||
export const GAS_PRICE_GWEI_UPPER_BOUND = 3000;
|
||||
export const GAS_PRICE_GWEI_DEFAULT = 20;
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
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';
|
||||
|
||||
export const languages = require('./languages.json');
|
||||
export const discordURL = 'https://discord.gg/VSaTXEA';
|
||||
|
||||
// Displays in the footer
|
||||
export const VERSION = '0.3.2 (BETA)';
|
||||
export const VERSION = `${packageJson.version} (BETA)`;
|
||||
export const N_FACTOR = 8192;
|
||||
|
||||
// Displays at the top of the site, make message empty string to remove.
|
||||
// Type can be primary, warning, danger, success, or info.
|
||||
// Type can be primary, warning, danger, success, info, or blank for grey.
|
||||
// Message must be a JSX element if you want to use HTML.
|
||||
export const ANNOUNCEMENT_TYPE = 'warning';
|
||||
export const ANNOUNCEMENT_TYPE = '';
|
||||
export const ANNOUNCEMENT_MESSAGE = (
|
||||
<div>
|
||||
This is an early build of MyCrypto Beta. Please only use for testing, or use production at{' '}
|
||||
<a href="https://mycrypto.com">{'https://mycrypto.com'}</a>.
|
||||
<br />
|
||||
<span className="hidden-xs">
|
||||
If you're interested in recieving updates about the MyCrypto Beta, you can subscribe via{' '}
|
||||
<a href="https://mycrypto.us17.list-manage.com/subscribe?u=6092be560275431280b0ffbc1&id=a03ef384e4">
|
||||
mailchimp
|
||||
</a>{' '}
|
||||
:)
|
||||
</span>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
This is a Beta version of MyCrypto. Please submit any bug reports to our{' '}
|
||||
<NewTabLink href="https://github.com/MyCryptoHQ/MyCrypto/issues">GitHub</NewTabLink>, and join
|
||||
the discussion on <NewTabLink href={discordURL}>Discord</NewTabLink>.
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const etherScan = 'https://etherscan.io';
|
||||
|
@ -41,12 +38,12 @@ export const donationAddressMap = {
|
|||
REP: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520'
|
||||
};
|
||||
|
||||
export const gasPriceDefaults = {
|
||||
minGwei: 1,
|
||||
maxGwei: 60,
|
||||
default: 21
|
||||
};
|
||||
export const gasEstimateCacheTime = 60000;
|
||||
export const gasPriceDefaults: GasPriceSetting = {
|
||||
min: 1,
|
||||
max: 60,
|
||||
initial: 20
|
||||
};
|
||||
|
||||
export const MINIMUM_PASSWORD_LENGTH = 12;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ export const ETH_LEDGER: DPath = {
|
|||
|
||||
export const ETC_LEDGER: DPath = {
|
||||
label: 'Ledger (ETC)',
|
||||
value: "m/44'/60'/160720'/0'"
|
||||
value: "m/44'/60'/160720'/0"
|
||||
};
|
||||
|
||||
export const ETC_TREZOR: DPath = {
|
||||
|
@ -43,5 +43,30 @@ export const ETH_SINGULAR: DPath = {
|
|||
value: "m/0'/0'/0'"
|
||||
};
|
||||
|
||||
export const DPaths: DPath[] = [
|
||||
ETH_DEFAULT,
|
||||
ETH_TREZOR,
|
||||
ETH_LEDGER,
|
||||
ETC_LEDGER,
|
||||
ETC_TREZOR,
|
||||
ETH_TESTNET,
|
||||
EXP_DEFAULT,
|
||||
UBQ_DEFAULT
|
||||
];
|
||||
|
||||
// PATHS TO BE INCLUDED REGARDLESS OF WALLET FORMAT
|
||||
export const EXTRA_PATHS = [ETH_SINGULAR];
|
||||
|
||||
// Full length deterministic wallet paths from BIP44
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
// normal path length is 4, ledger is the exception at 3
|
||||
|
||||
// m / purpose' / coin_type' / account' / change / address_index
|
||||
// | | | | |
|
||||
// | constant | index | index | 0 or 1 |
|
||||
// |__________|____________|__________|________|
|
||||
|
||||
// whitespace strings are evaluated the same way as nospace strings, except they allow optional spaces between each portion of the string
|
||||
// ie. "m / 44' / 0' / 0'" is valid, "m / 4 4' / 0' / 0'" is invalid
|
||||
export const dPathRegex = /m\/44'\/[0-9]+\'\/[0-9]+(\'+$|\'+(\/[0-1]+$))/;
|
||||
// export const whitespaceDPathRegex = /m\s*\/\s*44'\s*\/\s*[0-9]+\'\s*\/\s*[0-9]+(\'+$|\'+\s*(\/\s*[0-1]+$))/;
|
||||
|
|
|
@ -2,3 +2,4 @@ export * from './data';
|
|||
export * from './bity';
|
||||
export * from './addressMessages';
|
||||
export * from './helpArticles';
|
||||
export * from './dpaths';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AlphaAgreement, Footer, Header } from 'components';
|
||||
import { BetaAgreement, Footer, Header } from 'components';
|
||||
import { AppState } from 'reducers';
|
||||
import Notifications from './Notifications';
|
||||
import OfflineTab from './OfflineTab';
|
||||
|
@ -24,15 +24,14 @@ class TabSection extends Component<Props, {}> {
|
|||
|
||||
return (
|
||||
<div className="page-layout">
|
||||
<main>
|
||||
<Header />
|
||||
<div className="Tab container">
|
||||
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
|
||||
</div>
|
||||
<Footer latestBlock={latestBlock} />
|
||||
</main>
|
||||
<Header />
|
||||
<div className="Tab container">
|
||||
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
|
||||
</div>
|
||||
<div className="flex-spacer" />
|
||||
<Footer latestBlock={latestBlock} />
|
||||
<Notifications />
|
||||
<AlphaAgreement />
|
||||
<BetaAgreement />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,9 +9,8 @@ import {
|
|||
TSignTransactionFailed
|
||||
} from 'actions/transaction';
|
||||
import { computeIndexingHash } from 'libs/transaction';
|
||||
import { QRCode } from 'components/ui';
|
||||
import { QRCode, TextArea } from 'components/ui';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import classnames from 'classnames';
|
||||
import { SendButton } from 'components/SendButton';
|
||||
import { toBuffer, bufferToHex } from 'ethereumjs-util';
|
||||
import { getSerializedTransaction } from 'selectors/transaction';
|
||||
|
@ -40,11 +39,6 @@ class BroadcastTx extends Component<Props> {
|
|||
public render() {
|
||||
const { userInput } = this.state;
|
||||
const { stateTransaction } = this.props;
|
||||
const inputClasses = classnames({
|
||||
'form-control': true,
|
||||
'is-valid': !!stateTransaction,
|
||||
'is-invalid': !stateTransaction
|
||||
});
|
||||
const currentPath = this.props.match.url;
|
||||
return (
|
||||
<TabSection isUnavailableOffline={true}>
|
||||
|
@ -59,13 +53,19 @@ class BroadcastTx extends Component<Props> {
|
|||
<p className="BroadcastTx-help">
|
||||
Paste a signed transaction and press the "SEND TRANSACTION" button.
|
||||
</p>
|
||||
<label>{translateRaw('SEND_signed')}</label>
|
||||
<textarea
|
||||
className={inputClasses}
|
||||
rows={7}
|
||||
value={userInput}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<div className="input-group-wrapper InteractForm-interface">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translateRaw('SEND_signed')}</div>
|
||||
<TextArea
|
||||
className={stateTransaction ? '' : 'invalid'}
|
||||
rows={7}
|
||||
value={userInput}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<SendButton onlyTransactionParameters={true} />
|
||||
|
||||
<div className="BroadcastTx-qr">
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import translate from 'translations';
|
||||
import { isValidTxHash, isValidETHAddress } from 'libs/validators';
|
||||
import './TxHashInput.scss';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
hash?: string;
|
||||
|
@ -26,14 +27,14 @@ export default class TxHashInput extends React.Component<Props, State> {
|
|||
|
||||
public render() {
|
||||
const { hash } = this.state;
|
||||
const validClass = hash ? (isValidTxHash(hash) ? 'is-valid' : 'is-invalid') : '';
|
||||
const validClass = hash ? (isValidTxHash(hash) ? '' : 'invalid') : '';
|
||||
|
||||
return (
|
||||
<form className="TxHashInput" onSubmit={this.handleSubmit}>
|
||||
<input
|
||||
<Input
|
||||
value={hash}
|
||||
placeholder="0x16e521..."
|
||||
className={`TxHashInput-field form-control ${validClass}`}
|
||||
className={`TxHashInput-field ${validClass}`}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
|
|
|
@ -2,13 +2,10 @@
|
|||
&-field {
|
||||
margin-top: 0;
|
||||
|
||||
&-label {
|
||||
float: left;
|
||||
}
|
||||
|
||||
&-reset {
|
||||
float: right;
|
||||
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-bottom: 0.25rem;
|
||||
.fa {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { FullWalletOnly } from 'components/renderCbs';
|
|||
import { NonceField, TXMetaDataPanel } from 'components';
|
||||
import './Deploy.scss';
|
||||
import { ConfirmationModal } from 'components/ConfirmationModal';
|
||||
import { TextArea } from 'components/ui';
|
||||
|
||||
interface DispatchProps {
|
||||
setToField: TSetToField;
|
||||
|
@ -23,27 +24,30 @@ class DeployClass extends Component<DispatchProps> {
|
|||
public render() {
|
||||
const makeContent = () => (
|
||||
<main className="Deploy Tab-content-pane" role="main">
|
||||
<div className="Deploy-field form-group">
|
||||
<h3 className="Deploy-field-label">{translate('CONTRACT_ByteCode')}</h3>
|
||||
<button className="Deploy-field-reset btn btn-default btn-sm" onClick={this.changeWallet}>
|
||||
<i className="fa fa-refresh" />
|
||||
{translate('Change Wallet')}
|
||||
</button>
|
||||
<DataFieldFactory
|
||||
withProps={({ data: { raw, value }, onChange, readOnly }) => (
|
||||
<textarea
|
||||
name="byteCode"
|
||||
placeholder="0x8f87a973e..."
|
||||
rows={6}
|
||||
onChange={onChange}
|
||||
disabled={readOnly}
|
||||
className={classnames('Deploy-field-input', 'form-control', {
|
||||
'is-valid': value && value.length > 0
|
||||
})}
|
||||
value={raw}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<button className="Deploy-field-reset btn btn-default btn-sm" onClick={this.changeWallet}>
|
||||
<i className="fa fa-refresh" />
|
||||
{translate('Change Wallet')}
|
||||
</button>
|
||||
|
||||
<div className="input-group-wrapper Deploy-field">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_ByteCode')}</div>
|
||||
<DataFieldFactory
|
||||
withProps={({ data: { raw, value }, onChange, readOnly }) => (
|
||||
<TextArea
|
||||
name="byteCode"
|
||||
placeholder="0x8f87a973e..."
|
||||
rows={6}
|
||||
onChange={onChange}
|
||||
disabled={readOnly}
|
||||
className={classnames('Deploy-field-input', 'form-control', {
|
||||
'is-valid': value && value.length > 0
|
||||
})}
|
||||
value={raw}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="row form-group">
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import { AmountFieldFactory } from 'components/AmountFieldFactory';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Input } from 'components/ui';
|
||||
|
||||
export const AmountField: React.SFC<{}> = () => (
|
||||
<label className="InteractExplorer-field form-group">
|
||||
<h4 className="InteractExplorer-field-label">Value</h4>
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<input
|
||||
name="value"
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
readOnly={readOnly}
|
||||
className={classnames('InteractExplorer-field-input', 'form-control', {
|
||||
'is-invalid': !(isValid || raw === '')
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
export const AmountField: React.SFC = () => (
|
||||
<div className="input-group-wrapper InteractExplorer-field">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">Value</div>
|
||||
<AmountFieldFactory
|
||||
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
|
||||
<Input
|
||||
name="value"
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
readOnly={readOnly}
|
||||
className={classnames('InteractExplorer-field-input', 'form-control', {
|
||||
'is-invalid': !(isValid || raw === '')
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,9 +10,10 @@ import { connect } from 'react-redux';
|
|||
import { Fields } from './components';
|
||||
import { setDataField, TSetDataField } from 'actions/transaction';
|
||||
import { Data } from 'libs/units';
|
||||
import Select from 'react-select';
|
||||
import { Web3Node } from 'libs/nodes';
|
||||
import RpcNode from 'libs/nodes/rpc';
|
||||
import { Input } from 'components/ui';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
|
||||
interface StateProps {
|
||||
nodeLib: RpcNode | Web3Node;
|
||||
|
@ -83,22 +84,25 @@ class InteractExplorerClass extends Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div className="InteractExplorer">
|
||||
<h3 className="InteractExplorer-title">
|
||||
{translate('CONTRACT_Interact_Title')}
|
||||
<span className="InteractExplorer-title-address">{to.raw}</span>
|
||||
</h3>
|
||||
|
||||
<Select
|
||||
name="exploreContract"
|
||||
value={selectedFunction as any}
|
||||
placeholder="Please select a function..."
|
||||
onChange={this.handleFunctionSelect}
|
||||
options={contractFunctionsOptions}
|
||||
clearable={false}
|
||||
searchable={false}
|
||||
labelKey="name"
|
||||
valueKey="contract"
|
||||
/>
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">
|
||||
{translate('CONTRACT_Interact_Title')}
|
||||
<div className="flex-spacer" />
|
||||
<span className="small">{to.raw}</span>
|
||||
</div>
|
||||
<Dropdown
|
||||
name="exploreContract"
|
||||
value={selectedFunction as any}
|
||||
placeholder="Please select a function..."
|
||||
onChange={this.handleFunctionSelect}
|
||||
options={contractFunctionsOptions}
|
||||
clearable={false}
|
||||
labelKey="name"
|
||||
valueKey="contract"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{selectedFunction && (
|
||||
<div key={selectedFunction.name} className="InteractExplorer-func">
|
||||
|
@ -107,18 +111,17 @@ class InteractExplorerClass extends Component<Props, State> {
|
|||
const { type, name } = input;
|
||||
|
||||
return (
|
||||
<label key={name} className="InteractExplorer-func-in form-group">
|
||||
<h4 className="InteractExplorer-func-in-label">
|
||||
{name}
|
||||
<span className="InteractExplorer-func-in-label-type">{type}</span>
|
||||
</h4>
|
||||
<input
|
||||
className="InteractExplorer-func-in-input form-control"
|
||||
name={name}
|
||||
value={(inputs[name] && inputs[name].rawData) || ''}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</label>
|
||||
<div key={name} className="input-group-wrapper InteractExplorer-func-in">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{name + ' ' + type}</div>
|
||||
<Input
|
||||
className="InteractExplorer-func-in-input"
|
||||
name={name}
|
||||
value={(inputs[name] && inputs[name].rawData) || ''}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{selectedFunction.contract.outputs.map((output, index) => {
|
||||
|
@ -126,17 +129,16 @@ class InteractExplorerClass extends Component<Props, State> {
|
|||
const parsedName = name === '' ? index : name;
|
||||
|
||||
return (
|
||||
<label key={parsedName} className="InteractExplorer-func-out form-group">
|
||||
<h4 className="InteractExplorer-func-out-label">
|
||||
↳ {name}
|
||||
<span className="InteractExplorer-func-out-label-type">{type}</span>
|
||||
</h4>
|
||||
<input
|
||||
className="InteractExplorer-func-out-input form-control"
|
||||
value={outputs[parsedName] || ''}
|
||||
disabled={true}
|
||||
/>
|
||||
</label>
|
||||
<div key={parsedName} className="input-group-wrapper InteractExplorer-func-out">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header"> ↳ {name + ' ' + type}</div>
|
||||
<Input
|
||||
className="InteractExplorer-func-out-input "
|
||||
value={outputs[parsedName] || ''}
|
||||
disabled={true}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ import { connect } from 'react-redux';
|
|||
import { AppState } from 'reducers';
|
||||
import { isValidETHAddress, isValidAbiJson } from 'libs/validators';
|
||||
import classnames from 'classnames';
|
||||
import Select from 'react-select';
|
||||
import { NetworkContract } from 'types/network';
|
||||
import { donationAddressMap } from 'config';
|
||||
import { Input, TextArea } from 'components/ui';
|
||||
import Dropdown from 'components/ui/Dropdown';
|
||||
|
||||
interface ContractOption {
|
||||
name: string;
|
||||
|
@ -83,45 +84,44 @@ class InteractForm extends Component<Props, State> {
|
|||
return (
|
||||
<div className="InteractForm">
|
||||
<div className="InteractForm-address row">
|
||||
<label className="InteractForm-address-field form-group col-sm-6">
|
||||
<h4>{translate('CONTRACT_Title')}</h4>
|
||||
<input
|
||||
placeholder={`ensdomain.eth or ${donationAddressMap.ETH}`}
|
||||
name="contract_address"
|
||||
autoComplete="off"
|
||||
value={address}
|
||||
className={classnames('InteractForm-address-field-input', 'form-control', {
|
||||
'is-invalid': !validEthAddress
|
||||
})}
|
||||
onChange={this.handleInput('address')}
|
||||
/>
|
||||
</label>
|
||||
<div className="input-group-wrapper InteractForm-address-field col-sm-6">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_Title')}</div>
|
||||
<Input
|
||||
placeholder={`ensdomain.eth or ${donationAddressMap.ETH}`}
|
||||
name="contract_address"
|
||||
autoComplete="off"
|
||||
value={address}
|
||||
className={classnames('InteractForm-address-field-input', {
|
||||
invalid: !validEthAddress
|
||||
})}
|
||||
onChange={this.handleInput('address')}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="InteractForm-address-contract form-group col-sm-6">
|
||||
<h4>{translate('CONTRACT_Title_2')}</h4>
|
||||
<Select
|
||||
name="interactContract"
|
||||
className={`${!contract ? 'is-invalid' : ''}`}
|
||||
value={contract as any}
|
||||
placeholder={this.state.contractPlaceholder}
|
||||
onChange={this.handleSelectContract}
|
||||
options={contractOptions}
|
||||
clearable={false}
|
||||
searchable={false}
|
||||
labelKey="name"
|
||||
/>
|
||||
</label>
|
||||
<div className="input-group-wrapper InteractForm-address-field col-sm-6">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_Title_2')}</div>
|
||||
<Dropdown
|
||||
className={`${!contract ? 'invalid' : ''}`}
|
||||
value={contract as any}
|
||||
placeholder={this.state.contractPlaceholder}
|
||||
onChange={this.handleSelectContract}
|
||||
options={contractOptions}
|
||||
clearable={false}
|
||||
labelKey="name"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="InteractForm-interface">
|
||||
<label className="InteractForm-interface-field form-group">
|
||||
<h4 className="InteractForm-interface-field-label">{translate('CONTRACT_Json')}</h4>
|
||||
<textarea
|
||||
<div className="input-group-wrapper InteractForm-interface">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('CONTRACT_Json')}</div>
|
||||
<TextArea
|
||||
placeholder={this.abiJsonPlaceholder}
|
||||
name="abiJson"
|
||||
className={classnames('InteractForm-interface-field-input', 'form-control', {
|
||||
'is-invalid': !validAbiJson
|
||||
})}
|
||||
className={`InteractForm-interface-field-input ${validAbiJson ? '' : 'invalid'}`}
|
||||
onChange={this.handleInput('abiJson')}
|
||||
value={abiJson}
|
||||
rows={6}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.Contracts {
|
||||
&-header {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
|
||||
&-tab {
|
||||
@include reset-button;
|
||||
color: $ether-blue;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
&,
|
||||
&:hover,
|
||||
&:active {
|
||||
color: $text-color;
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import translate from 'translations';
|
||||
import { Interact } from './components/Interact';
|
||||
import { Deploy } from './components/Deploy';
|
||||
import './index.scss';
|
||||
import { reset, TReset } from 'actions/transaction';
|
||||
import { resetWallet, TResetWallet } from 'actions/wallet';
|
||||
import TabSection from 'containers/TabSection';
|
||||
|
|