Merge pull request #1226 from MyCryptoHQ/develop

Tag Beta Release 0.4.0
This commit is contained in:
Daniel Ternyak 2018-03-02 00:35:23 -06:00 committed by GitHub
commit b445a43488
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
208 changed files with 2565 additions and 1968 deletions

2
.gitignore vendored
View File

@ -56,3 +56,5 @@ webpack_config/server.csr
v8-compile-cache-0/
package-lock.json
yarn.lock

View File

@ -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);

View File

@ -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']

View File

@ -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 {

View File

@ -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',

View File

@ -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 {

View File

@ -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

View File

@ -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',

View File

@ -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,

View File

@ -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 };

View File

@ -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 =

View File

@ -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
}));
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -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

View File

@ -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>
)}
/>
);

View File

@ -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 }) =>

View File

@ -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');
};
}

View File

@ -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>
)}
/>
);

View File

@ -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;
}

View File

@ -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>

View File

@ -41,7 +41,6 @@
flex-wrap: nowrap;
align-items: center;
&-fiat-symbol {
height: 18px;
width: 18px;
margin-right: 0.5rem;
text-align: center;

View File

@ -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()) {

View File

@ -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}

View File

@ -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;

View File

@ -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');
};
}

View File

@ -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>

View File

@ -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>
)}
/>
);

View File

@ -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>
);

View File

@ -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;

View File

@ -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,

View File

@ -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'
}
];

View File

@ -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>
)}
/>
);

View File

@ -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

View File

@ -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
}
}
: {})

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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"

View File

@ -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;
}
}

View File

@ -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;
}}
/>

View File

@ -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 />

View File

@ -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

View File

@ -16,7 +16,7 @@
transition-duration: 100ms;
&.is-active {
border-bottom-color: $ether-blue;
border-bottom-color: $brand-primary;
}
&.is-disabled {

View File

@ -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 }
});

View File

@ -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>
)}

View File

@ -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());
}
}

View File

@ -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}

View File

@ -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
}
];

View File

@ -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)
};
}

View File

@ -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}

View File

@ -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}>

View File

@ -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' : '';

View File

@ -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')}

View File

@ -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}>

View File

@ -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' : '';

View File

@ -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}

View File

@ -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';

View File

@ -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;

View File

@ -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);

View File

@ -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();
}
};
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -111,7 +111,7 @@ $m-anim-speed: 400ms;
// Mobile styles
@media(max-width: $screen-sm) {
width: calc(100% - 40px);
width: calc(100% - 40px) !important;
}
}

View File

@ -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>

View File

@ -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();
}
};
}

View File

@ -0,0 +1,7 @@
.SimpleButton {
display: flex;
align-items: center;
> .Spinner {
margin-right: 16px;
}
}

View File

@ -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>

View File

@ -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'}
/>
);
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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';

View File

@ -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;

View File

@ -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;

View File

@ -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]+$))/;

View File

@ -2,3 +2,4 @@ export * from './data';
export * from './bity';
export * from './addressMessages';
export * from './helpArticles';
export * from './dpaths';

View File

@ -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>
);
}

View File

@ -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">

View File

@ -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}
/>

View File

@ -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;
}

View File

@ -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">

View File

@ -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>
);

View File

@ -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>
);
})}

View File

@ -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}

View File

@ -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;
}
}
}
}
}

View File

@ -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';

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